| 1 |
#' FileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' Comparator 'abstract' class containing the generic comparison methods and |
|
| 4 |
#' handling for high level checks (like file existence). This class should never |
|
| 5 |
#' be instantiated - doing that and calling the comparison methods will lead to |
|
| 6 |
#' error. |
|
| 7 |
#' |
|
| 8 |
#' @importFrom R6 R6Class |
|
| 9 |
#' |
|
| 10 |
#' @field file1 file1 |
|
| 11 |
#' @field file2 file2 |
|
| 12 |
#' @field file1_contents_list file1 contents |
|
| 13 |
#' @field file2_contents_list file2 contents |
|
| 14 |
#' @field summary_comparison summary comparison result |
|
| 15 |
#' @field details_comparison details comparison result |
|
| 16 |
#' @field debugger debugger instance |
|
| 17 |
#' |
|
| 18 |
#' @export |
|
| 19 |
#' |
|
| 20 | ||
| 21 |
# Disable cyclomatic complexity lint for the R6 class definition as lintr |
|
| 22 |
# considers the whole class definition as a single function. |
|
| 23 |
# |
|
| 24 |
# nolint start: cyclocomp_linter |
|
| 25 |
FileComparator <- R6::R6Class( |
|
| 26 |
"FileComparator", |
|
| 27 |
public <- list( |
|
| 28 |
file1 = NULL, |
|
| 29 |
file2 = NULL, |
|
| 30 |
file1_contents_list = NULL, |
|
| 31 |
file2_contents_list = NULL, |
|
| 32 |
summary_comparison = NULL, |
|
| 33 |
details_comparison = NULL, |
|
| 34 |
debugger = NULL, |
|
| 35 | ||
| 36 |
#' @description |
|
| 37 |
#' Initialize a FileComparator instance |
|
| 38 |
#' |
|
| 39 |
#' @param file1 First file to compare. |
|
| 40 |
#' @param file2 Second file to compare. |
|
| 41 |
#' |
|
| 42 |
initialize = function(file1 = NULL, file2 = NULL) {
|
|
| 43 | 96x |
self$file1 <- file1 |
| 44 | 96x |
self$file2 <- file2 |
| 45 | 96x |
self$details_comparison <- list("summary" = NULL, "full" = NULL)
|
| 46 |
}, |
|
| 47 | ||
| 48 |
#' @description |
|
| 49 |
#' Method for comparing the file summary information. This method is |
|
| 50 |
#' intended to be implemented only this class level. For comparator |
|
| 51 |
#' specific rules, the internal method vrf_summary_inner should be |
|
| 52 |
#' customized on lower levels instead. |
|
| 53 |
#' |
|
| 54 |
#' @param config configuration values |
|
| 55 |
#' @param omit string pattern to omit from the comparison |
|
| 56 |
#' |
|
| 57 |
vrf_summary = function(config, omit = NULL) {
|
|
| 58 | 68x |
self$vrf_open_debug("File::vrf_summary", config)
|
| 59 | 68x |
self$vrf_add_debug_files() |
| 60 | ||
| 61 | 68x |
if (!is.null(self$summary_comparison)) {
|
| 62 | ! |
self$vrf_add_debug("Returning previously calculated comparison results")
|
| 63 | ! |
return(self$summary_comparison) |
| 64 |
} |
|
| 65 | ||
| 66 | 68x |
if (!file.exists(self$file1) || !file.exists(self$file2)) {
|
| 67 | 9x |
self$vrf_add_debug("One of both of the files not available, unable perform comparison")
|
| 68 | 9x |
result <- "File(s) not available; unable to compare." |
| 69 |
} else {
|
|
| 70 | 59x |
tryCatch({
|
| 71 | 59x |
result <- self$vrf_summary_inner(config, omit) |
| 72 | 59x |
addition <- self$vrf_details_supported(config) |
| 73 | ||
| 74 | 59x |
if ('' != addition) {
|
| 75 | 5x |
result <- paste(result, addition) |
| 76 |
} |
|
| 77 | 59x |
}, error = function(e) {
|
| 78 | ! |
self$vrf_add_debug(paste("Processing failed with exception: ", conditionMessage(e)))
|
| 79 | ! |
result <- paste0("Error reading file contents: ", conditionMessage(e))
|
| 80 |
}) |
|
| 81 | ||
| 82 |
} |
|
| 83 | ||
| 84 | 68x |
self$summary_comparison <- result |
| 85 | 68x |
self$vrf_close_debug() |
| 86 | ||
| 87 | 68x |
return(result) |
| 88 |
}, |
|
| 89 | ||
| 90 |
#' @description |
|
| 91 |
#' Method for comparing the file details information. This method is |
|
| 92 |
#' intended to be implemented only this class level. For comparator |
|
| 93 |
#' specific rules, the internal method vrf_summary_inner should be |
|
| 94 |
#' customized on lower levels instead. |
|
| 95 |
#' |
|
| 96 |
#' @param config configuration values |
|
| 97 |
#' @param omit string pattern to omit from the comparison |
|
| 98 |
#' |
|
| 99 |
vrf_details = function(config, omit = NULL) {
|
|
| 100 | 26x |
mode <- self$vrf_option_value(config, "details.mode") |
| 101 | 26x |
if ("NA" == mode) {
|
| 102 | ! |
mode <- "summary" |
| 103 |
} |
|
| 104 | ||
| 105 | 26x |
self$vrf_open_debug(paste("File::vrf_details, mode:", mode), config)
|
| 106 | 26x |
self$vrf_add_debug_files() |
| 107 | ||
| 108 | 26x |
if (!is.null(self$details_comparison[[mode]])) {
|
| 109 | ! |
self$vrf_add_debug("Returning previously calculated comparison results")
|
| 110 | ! |
return(self$details_comparison[[mode]]) |
| 111 |
} |
|
| 112 | ||
| 113 | 26x |
if (!file.exists(self$file1) || !file.exists(self$file2)) {
|
| 114 | 11x |
self$vrf_add_debug("One of both of the files not available, unable perform comparison")
|
| 115 | 11x |
result <- list( |
| 116 | 11x |
list( |
| 117 | 11x |
type = "text", |
| 118 | 11x |
contents = "File(s) not available; unable to compare." |
| 119 |
) |
|
| 120 |
) |
|
| 121 | 15x |
} else if ('' != self$vrf_details_supported(config)) {
|
| 122 | 1x |
self$vrf_add_debug("Details comparison not supported/enabled, unable perform comparison")
|
| 123 | 1x |
result <- list( |
| 124 | 1x |
list( |
| 125 | 1x |
type = "text", |
| 126 | 1x |
contents = self$vrf_details_supported(config) |
| 127 |
) |
|
| 128 |
) |
|
| 129 |
} else {
|
|
| 130 | 14x |
tryCatch({
|
| 131 | 14x |
result <- self$vrf_details_inner(config, omit) |
| 132 | 14x |
}, error = function(e) {
|
| 133 | ! |
self$vrf_add_debug(paste("Processing failed with exception: ", conditionMessage(e)))
|
| 134 | ! |
result <- list( |
| 135 | ! |
list( |
| 136 | ! |
type = "text", |
| 137 | ! |
contents = paste0("Error reading file contents: ", conditionMessage(e))
|
| 138 |
) |
|
| 139 |
) |
|
| 140 |
}) |
|
| 141 |
} |
|
| 142 | ||
| 143 | 26x |
self$details_comparison[[mode]] <- result |
| 144 | 26x |
self$vrf_close_debug() |
| 145 | ||
| 146 | 26x |
return(result) |
| 147 |
}, |
|
| 148 | ||
| 149 |
#' @description |
|
| 150 |
#' "Abstract" method for comparing the inner part for the summary. This |
|
| 151 |
#' method has to be overwritten by more specialized comparator classes. This |
|
| 152 |
#' method is intended to be called only by the comparator classes in the |
|
| 153 |
#' processing and shouldn't be called directly by the user. |
|
| 154 |
#' |
|
| 155 |
#' @param config configuration values |
|
| 156 |
#' @param omit string pattern to omit from the comparison |
|
| 157 |
#' |
|
| 158 |
vrf_summary_inner = function(config, omit) {
|
|
| 159 | ! |
stop("vrf_summary_inner must be implemented in a subclass.")
|
| 160 |
}, |
|
| 161 | ||
| 162 |
#' @description |
|
| 163 |
#' "Abstract" method for comparing the inner part for the detailsThis method |
|
| 164 |
#' has to be overwritten by more specialized comparator classes. This method |
|
| 165 |
#' is intended to be called only by the comparator classes in the processing |
|
| 166 |
#' and shouldn't be called directly by the user. |
|
| 167 |
#' |
|
| 168 |
#' @param config configuration values |
|
| 169 |
#' @param omit string pattern to omit from the comparison |
|
| 170 |
#' |
|
| 171 |
vrf_details_inner = function(config, omit) {
|
|
| 172 | ! |
stop("vrf_details_inner must be implemented in a subclass.")
|
| 173 |
}, |
|
| 174 | ||
| 175 |
#' @description |
|
| 176 |
#' Inherited method for indicating whether detailed comparison is available |
|
| 177 |
#' with the current comparator. Returns an empty string if the comparator is |
|
| 178 |
#' is supported, otherwise a string that will be concatenated with the |
|
| 179 |
#' summary string. |
|
| 180 |
#' |
|
| 181 |
#' @param config configuration values |
|
| 182 |
#' |
|
| 183 |
vrf_details_supported = function(config) {
|
|
| 184 | 5x |
return("No details comparison available.")
|
| 185 |
}, |
|
| 186 | ||
| 187 |
#' @description |
|
| 188 |
#' Method for getting specific value from the config In the initial |
|
| 189 |
#' version, returns 'NA' if null con is passed. |
|
| 190 |
#' |
|
| 191 |
#' @param config configuration values |
|
| 192 |
#' @param key key to search from the parameters |
|
| 193 |
#' |
|
| 194 |
vrf_option_value = function(config, key) {
|
|
| 195 | 738x |
if (is.null(config)) {
|
| 196 | ! |
return("NA")
|
| 197 |
} |
|
| 198 | 738x |
value <- config$get(key) |
| 199 | 738x |
return(value) |
| 200 |
}, |
|
| 201 | ||
| 202 |
#' @description |
|
| 203 |
#' Wrapper method for the opening a new debugging instance with Debugger |
|
| 204 |
#' class if debugging is enabled in config class. Creates the used |
|
| 205 |
#' debugger instance if needed. |
|
| 206 |
#' |
|
| 207 |
#' @param message message to debug to console |
|
| 208 |
#' @param config configuration values |
|
| 209 |
#' |
|
| 210 |
vrf_open_debug = function(message, config) {
|
|
| 211 | 516x |
if ("yes" != self$vrf_option_value(config, "generic.debug")) {
|
| 212 | 505x |
return() |
| 213 |
} |
|
| 214 | ||
| 215 | 11x |
if (is.null(self$debugger)) {
|
| 216 | 2x |
self$debugger <- Debugger$new() |
| 217 |
} |
|
| 218 | ||
| 219 | 11x |
self$debugger$open_debug(message) |
| 220 |
}, |
|
| 221 | ||
| 222 |
#' @description |
|
| 223 |
#' Wrapper method for the adding a new debugging message with Debugger |
|
| 224 |
#' class. |
|
| 225 |
#' |
|
| 226 |
#' @param message message to debug to console |
|
| 227 |
#' |
|
| 228 |
vrf_add_debug = function(message) {
|
|
| 229 | 27x |
if (is.null(self$debugger)) {
|
| 230 | 27x |
return() |
| 231 |
} |
|
| 232 | ||
| 233 | ! |
self$debugger$add_debug(message) |
| 234 |
}, |
|
| 235 | ||
| 236 |
#' @description |
|
| 237 |
#' Special method for adding the compared files into debugger stack. |
|
| 238 |
#' |
|
| 239 |
vrf_add_debug_files = function() {
|
|
| 240 | 94x |
if (is.null(self$debugger)) {
|
| 241 | 93x |
return() |
| 242 |
} |
|
| 243 | ||
| 244 | 1x |
self$debugger$add_debug(paste("File 1:", self$file1))
|
| 245 | 1x |
self$debugger$add_debug(paste("File 2:", self$file2))
|
| 246 |
}, |
|
| 247 | ||
| 248 |
#' @description |
|
| 249 |
#' Wrapper method for the stopping (closing) current debugging instance with |
|
| 250 |
#' Debugger class. |
|
| 251 |
#' |
|
| 252 |
vrf_close_debug = function() {
|
|
| 253 | 516x |
if (is.null(self$debugger)) {
|
| 254 | 505x |
return() |
| 255 |
} |
|
| 256 | 11x |
self$debugger$close_debug() |
| 257 |
} |
|
| 258 |
) |
|
| 259 |
) |
|
| 260 |
# nolint end: cyclocomp_linter |
| 1 |
#' TextFileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' Fallback comparator for text files without any specific definied comparator. |
|
| 4 |
#' This comparator contains the methods for doing basic comparisons on raw text |
|
| 5 |
#' contents. |
|
| 6 |
#' |
|
| 7 |
#' @import stringr |
|
| 8 |
#' |
|
| 9 |
#' @include BinaryFileComparator.R |
|
| 10 |
#' |
|
| 11 |
#' @examples |
|
| 12 |
#' |
|
| 13 |
#' # The normal way for creating a comparator would be to call the generic |
|
| 14 |
#' # factory method verifyr2::create_comparator that will automatically create |
|
| 15 |
#' # the correct comparator instance based on the file types. |
|
| 16 |
#' |
|
| 17 |
#' file1 <- 'my_file1.txt' |
|
| 18 |
#' file2 <- 'my_file2.txt' |
|
| 19 |
#' comparator <- verifyr2::create_comparator(file1, file2) |
|
| 20 |
#' |
|
| 21 |
#' # If needed, an explicit comparator can be created as well. |
|
| 22 |
#' |
|
| 23 |
#' file1 <- 'my_file1.lst' |
|
| 24 |
#' file2 <- 'my_file2.lst' |
|
| 25 |
#' comparator <- TxtFileComparator$new(file1, file2) |
|
| 26 |
#' |
|
| 27 |
#' @export |
|
| 28 |
#' |
|
| 29 | ||
| 30 |
# Disable cyclomatic complexity lint for the R6 class definition as lintr |
|
| 31 |
# considers the whole class definition as a single function. |
|
| 32 |
# |
|
| 33 |
# nolint start: cyclocomp_linter |
|
| 34 |
TxtFileComparator <- R6::R6Class( |
|
| 35 |
"TxtFileComparator", |
|
| 36 |
inherit = BinaryFileComparator, |
|
| 37 |
public = list( |
|
| 38 | ||
| 39 |
#' @description |
|
| 40 |
#' Method for comparing the inner part for the details query. This method |
|
| 41 |
#' can be overwritten by more specialized comparator classes. This method is |
|
| 42 |
#' intended to be called only by the comparator classes in the processing |
|
| 43 |
#' and shouldn't be called directly by the user. |
|
| 44 |
#' |
|
| 45 |
#' @param config configuration values |
|
| 46 |
#' @param omit string pattern to omit from the comparison |
|
| 47 |
#' |
|
| 48 |
vrf_summary_inner = function(config, omit) {
|
|
| 49 | 53x |
self$vrf_open_debug("Txt::vrf_summary_inner" , config)
|
| 50 | ||
| 51 | 53x |
file1_contents_list <- self$file1_contents_list |
| 52 | 53x |
file2_contents_list <- self$file2_contents_list |
| 53 | ||
| 54 | 53x |
if (is.null(file1_contents_list)) {
|
| 55 | 53x |
file1_contents_list <- self$vrf_contents(self$file1, config, omit) |
| 56 | 53x |
self$file1_contents_list <- file1_contents_list |
| 57 |
} |
|
| 58 | ||
| 59 | 53x |
if (is.null(file2_contents_list)) {
|
| 60 | 53x |
file2_contents_list <- self$vrf_contents(self$file2, config, omit) |
| 61 | 53x |
self$file2_contents_list <- file2_contents_list |
| 62 |
} |
|
| 63 | ||
| 64 | 53x |
file1_contents_omit <- file1_contents_list[[2]] |
| 65 | 53x |
file2_contents_omit <- file2_contents_list[[2]] |
| 66 | ||
| 67 | 53x |
difference <- all.equal(file1_contents_omit, file2_contents_omit) |
| 68 | 53x |
result <- "File content comparison failed!" |
| 69 | 53x |
result_images <- "" |
| 70 | 53x |
pattern <- "Lengths \\((\\d+), (\\d+)\\) differ \\(string compare on first" |
| 71 | ||
| 72 | 53x |
if (typeof(difference) == "logical") {
|
| 73 |
# all.equal returns logical vector if there are no differences |
|
| 74 | 27x |
result <- "No differences." |
| 75 | 26x |
} else if (length(difference) >= 1 && grepl(pattern, difference[1])) {
|
| 76 |
# all.equal returns length 1/2 vector with first element comtaining text |
|
| 77 |
# matching the pattern |
|
| 78 | 12x |
result <- "Different number of lines in compared content." |
| 79 | 14x |
} else if (length(difference) == 1) {
|
| 80 |
# all.equal returns length 1 vector if the number of rows are the same but |
|
| 81 |
# there are differences |
|
| 82 | 14x |
count <- as.numeric(gsub("[^[:digit:].]", "", difference))
|
| 83 | 14x |
result <- paste0("File content has changes in ", count, " place(s).")
|
| 84 |
} |
|
| 85 | ||
| 86 | 53x |
self$vrf_close_debug() |
| 87 | 53x |
return(result) |
| 88 |
}, |
|
| 89 | ||
| 90 |
#' @description |
|
| 91 |
#' Method for comparing the inner part for the details query. This method |
|
| 92 |
#' can be overwritten by more specialized comparator classes. This method is |
|
| 93 |
#' intended to be called only by the comparator classes in the processing |
|
| 94 |
#' and shouldn't be called directly by the user. |
|
| 95 |
#' |
|
| 96 |
#' @param config configuration values |
|
| 97 |
#' @param omit string pattern to omit from the comparison |
|
| 98 |
#' |
|
| 99 |
vrf_details_inner = function(config, omit) {
|
|
| 100 | 12x |
self$vrf_open_debug("Txt::vrf_details_inner" , config)
|
| 101 | ||
| 102 | 12x |
file1_contents_list <- self$file1_contents_list |
| 103 | 12x |
file2_contents_list <- self$file2_contents_list |
| 104 | ||
| 105 | 12x |
if (is.null(file1_contents_list)) {
|
| 106 | 12x |
file1_contents_list <- self$vrf_contents(self$file1, config, omit) |
| 107 | 12x |
self$file1_contents_list <- file1_contents_list |
| 108 |
} |
|
| 109 | ||
| 110 | 12x |
if (is.null(file2_contents_list)) {
|
| 111 | 12x |
file2_contents_list <- self$vrf_contents(self$file2, config, omit) |
| 112 | 12x |
self$file2_contents_list <- file2_contents_list |
| 113 |
} |
|
| 114 | ||
| 115 | 12x |
file1_contents_whole <- file1_contents_list[[1]] |
| 116 | 12x |
file2_contents_whole <- file2_contents_list[[1]] |
| 117 | ||
| 118 | 12x |
context <- 2 |
| 119 | 12x |
if ("full" == super$vrf_option_value(config, "details.mode")) {
|
| 120 | 3x |
context <- -1 |
| 121 |
} |
|
| 122 | ||
| 123 | 12x |
my_equalizer_with_omit <- function(x, x.chr) {
|
| 124 | 3x |
my_finalizer(x, x.chr, omit) |
| 125 |
} |
|
| 126 | ||
| 127 | 12x |
style <- diffobj::StyleHtmlLightRgb( |
| 128 | 12x |
html.output = "diff.w.style", |
| 129 | 12x |
finalizer = my_equalizer_with_omit |
| 130 |
) |
|
| 131 | ||
| 132 | 12x |
diff_print <- diffobj::diffPrint( |
| 133 | 12x |
file1_contents_whole, |
| 134 | 12x |
file2_contents_whole, |
| 135 | 12x |
context = context, |
| 136 | 12x |
style = style |
| 137 |
) |
|
| 138 | ||
| 139 | 12x |
result <- list( |
| 140 | 12x |
list( |
| 141 | 12x |
type = "text", |
| 142 | 12x |
contents = diff_print |
| 143 |
) |
|
| 144 |
) |
|
| 145 | ||
| 146 | 12x |
self$vrf_close_debug() |
| 147 | 12x |
return(result) |
| 148 |
}, |
|
| 149 | ||
| 150 |
#' @description |
|
| 151 |
#' Method for getting the inner part for the file contents query. The method |
|
| 152 |
#' returns the file contents in two separate vectors inside a list. The |
|
| 153 |
#' first vector is the file contents and the second one is the file contents |
|
| 154 |
#' with the rows matching the omit string excluded. This method can be |
|
| 155 |
#' overwritten by more specialized comparator classes. This method is |
|
| 156 |
#' intended to be called only by the comparator classes in the processing |
|
| 157 |
#' and shouldn't be called directly by the user. |
|
| 158 |
#' |
|
| 159 |
#' @param contents file contents |
|
| 160 |
#' @param config configuration values |
|
| 161 |
#' @param omit string pattern to omit from the comparison |
|
| 162 |
#' |
|
| 163 |
vrf_contents_inner = function(contents, config, omit) {
|
|
| 164 | 130x |
self$vrf_open_debug("Txt::vrf_contents_inner" , config)
|
| 165 | ||
| 166 | 130x |
contents_omit <- contents |
| 167 | ||
| 168 | 130x |
if (!is.null(omit) && "" != paste0(omit)) {
|
| 169 | 54x |
contents_omit <- stringr::str_subset( |
| 170 | 54x |
string = contents, |
| 171 | 54x |
pattern = paste0(omit), |
| 172 | 54x |
negate = TRUE |
| 173 |
) |
|
| 174 |
} |
|
| 175 | ||
| 176 | 130x |
self$vrf_close_debug() |
| 177 | 130x |
return(list(contents, contents_omit)) |
| 178 |
}, |
|
| 179 | ||
| 180 |
#' @description |
|
| 181 |
#' Inherited method for indicating whether detailed comparison is available |
|
| 182 |
#' with the current comparator. Returns an empty string if the comparator is |
|
| 183 |
#' is supported, otherwise a string that will be concatenated with the |
|
| 184 |
#' summary string. |
|
| 185 |
#' |
|
| 186 |
#' @param config configuration values |
|
| 187 |
#' |
|
| 188 |
vrf_details_supported = function(config) {
|
|
| 189 | 64x |
return("")
|
| 190 |
} |
|
| 191 |
) |
|
| 192 |
) |
|
| 193 |
# nolint end: cyclocomp_linter |
|
| 194 | ||
| 195 |
#' Custom finalizer method for diffobj html content finalizing. This method is |
|
| 196 |
#' used to modify the diff html output so that omitted rows have their own |
|
| 197 |
#' special styling and gutters. |
|
| 198 |
#' |
|
| 199 |
#' @param x comparator instance used for the comparison that is meant to |
|
| 200 |
#' be created with the factory method vrf_comparator. |
|
| 201 |
#' @param x.chr character text representation of x, typically generated with |
|
| 202 |
#' the as character |
|
| 203 |
#' @param omit all lines containing the omit string will be excluded from the |
|
| 204 |
#' comparison (detaulf = NULL) |
|
| 205 |
#' |
|
| 206 |
#' @keywords internal |
|
| 207 | ||
| 208 |
my_finalizer <- function(x, x.chr, omit) {
|
|
| 209 | ||
| 210 | 3x |
split <- strsplit(x.chr, "<div class='diffobj-row'>")[[1]] |
| 211 | ||
| 212 | 3x |
if (!is.null(omit) && "" != paste0(omit)) {
|
| 213 | 1x |
for (i in seq_along(split)) {
|
| 214 | 9x |
if (grepl(omit, split[[i]])) {
|
| 215 | 1x |
row <- split[[i]] |
| 216 | ||
| 217 |
# modifying maching row markup |
|
| 218 | 1x |
row <- gsub( |
| 219 | 1x |
"class='diffobj-match'", |
| 220 | 1x |
"class='ignore'", |
| 221 | 1x |
row |
| 222 |
) |
|
| 223 | ||
| 224 | 1x |
row <- gsub( |
| 225 | 1x |
"<div class='diffobj-gutter'><div class='ignore'> ", |
| 226 | 1x |
"<div class='diffobj-gutter'><div class='ignore'>x", |
| 227 | 1x |
row |
| 228 |
) |
|
| 229 | ||
| 230 |
# modifying inserted row markup |
|
| 231 | 1x |
row <- gsub( |
| 232 | 1x |
"class='insert'", |
| 233 | 1x |
"class='ignore'", |
| 234 | 1x |
row |
| 235 |
) |
|
| 236 | ||
| 237 | 1x |
row <- gsub( |
| 238 | 1x |
"class='diffobj-word insert'", |
| 239 | 1x |
"class='diffobj-word ignore'", |
| 240 | 1x |
row |
| 241 |
) |
|
| 242 | ||
| 243 | 1x |
row <- gsub( |
| 244 | 1x |
"<div class='diffobj-gutter'><div class='ignore'>>", |
| 245 | 1x |
"<div class='diffobj-gutter'><div class='ignore'>X", |
| 246 | 1x |
row |
| 247 |
) |
|
| 248 | ||
| 249 |
# modifying deleted row markup |
|
| 250 | 1x |
row <- gsub( |
| 251 | 1x |
"class='delete'", |
| 252 | 1x |
"class='ignore'", |
| 253 | 1x |
row |
| 254 |
) |
|
| 255 | ||
| 256 | 1x |
row <- gsub( |
| 257 | 1x |
"class='diffobj-word delete'", |
| 258 | 1x |
"class='diffobj-word ignore'", |
| 259 | 1x |
row |
| 260 |
) |
|
| 261 | ||
| 262 | 1x |
row <- gsub( |
| 263 | 1x |
"<div class='diffobj-gutter'><div class='ignore'><", |
| 264 | 1x |
"<div class='diffobj-gutter'><div class='ignore'>X", |
| 265 | 1x |
row |
| 266 |
) |
|
| 267 | ||
| 268 |
# highlight the ommitted part |
|
| 269 | 1x |
row <- gsub( |
| 270 | 1x |
omit, |
| 271 | 1x |
paste0( |
| 272 | 1x |
"<span class='diffobj-word-highlight ignore'>", |
| 273 | 1x |
omit, |
| 274 | 1x |
"</span>" |
| 275 |
), |
|
| 276 | 1x |
row |
| 277 |
) |
|
| 278 | ||
| 279 | 1x |
split[[i]] <- row |
| 280 |
} |
|
| 281 |
} |
|
| 282 |
} |
|
| 283 | ||
| 284 | 3x |
html_string <- paste(split, collapse = "<div class='diffobj-row'>") |
| 285 | ||
| 286 | 3x |
diffobj::finalizeHtml(x, html_string) |
| 287 |
} |
| 1 | ||
| 2 |
#' Debugger.R |
|
| 3 |
#' |
|
| 4 |
#' Class for managing the library debugging. Tracks the debugged method |
|
| 5 |
#' execution times and prints out the full debugging information only when |
|
| 6 |
#' the main debugging instance is stopped. |
|
| 7 |
#' |
|
| 8 |
#' @examples |
|
| 9 |
#' |
|
| 10 |
#' # Creates a debugger instance. |
|
| 11 |
#' |
|
| 12 |
#' debugger <- Debugger$new() |
|
| 13 |
#' |
|
| 14 |
#' # Opening and closing debugs in multileveled function calls. |
|
| 15 |
#' |
|
| 16 |
#' function1 <- function() {
|
|
| 17 |
#' debugger$open_debug("function1")
|
|
| 18 |
#' function2() |
|
| 19 |
#' debuger$close_debug() |
|
| 20 |
#' } |
|
| 21 |
#' |
|
| 22 |
#' function2 <- function() {
|
|
| 23 |
#' debugger$open_debug("function2")
|
|
| 24 |
#' Sys.sleep(1) |
|
| 25 |
#' debugger$close_debug() |
|
| 26 |
#' } |
|
| 27 |
#' |
|
| 28 |
#' # This will produce the following printout to the console after the |
|
| 29 |
#' # function1 finishes |
|
| 30 |
#' # |
|
| 31 |
#' # - 'function1' (execution time 7 ms) |
|
| 32 |
#' # - 'function2' (execution time 5 ms) |
|
| 33 |
#' |
|
| 34 |
#' @importFrom R6 R6Class |
|
| 35 |
#' |
|
| 36 |
#' @field stack local property for storing debugging labels and start times |
|
| 37 |
#' |
|
| 38 |
#' @export |
|
| 39 |
#' |
|
| 40 |
Debugger <- R6::R6Class("Debugger",
|
|
| 41 |
public = list( |
|
| 42 |
stack = list(), |
|
| 43 | ||
| 44 |
#' @description |
|
| 45 |
#' Constructor for initializing the Debugger instance. |
|
| 46 |
#' |
|
| 47 |
initialize = function() {
|
|
| 48 | 5x |
self$stack <- list() |
| 49 |
}, |
|
| 50 | ||
| 51 |
#' @description |
|
| 52 |
#' Method for opening new debug instance to the current debugging stack. |
|
| 53 |
#' Stores also the start time for execution time calculation. |
|
| 54 |
#' |
|
| 55 |
#' @param label debugging message |
|
| 56 |
#' |
|
| 57 |
open_debug = function(label) {
|
|
| 58 | 16x |
entry <- list( |
| 59 | 16x |
label = label, |
| 60 | 16x |
start_time = Sys.time(), |
| 61 | 16x |
children = list(), |
| 62 | 16x |
duration = NULL, |
| 63 | 16x |
is_message = FALSE |
| 64 |
) |
|
| 65 | 16x |
self$stack[[length(self$stack) + 1]] <- entry |
| 66 |
}, |
|
| 67 | ||
| 68 |
#' @description |
|
| 69 |
#' Method for adding a new debug string under the currently open |
|
| 70 |
#' debug instance. |
|
| 71 |
#' |
|
| 72 |
#' @param label debugging message |
|
| 73 |
#' |
|
| 74 |
add_debug = function(label) {
|
|
| 75 | 4x |
msg <- list( |
| 76 | 4x |
label = label, |
| 77 | 4x |
children = list(), |
| 78 | 4x |
duration = NULL, |
| 79 | 4x |
is_message = TRUE |
| 80 |
) |
|
| 81 | ||
| 82 | 4x |
parent <- self$stack[[length(self$stack)]] |
| 83 | 4x |
parent$children[[length(parent$children) + 1]] <- msg |
| 84 | 4x |
self$stack[[length(self$stack)]] <- parent |
| 85 |
}, |
|
| 86 | ||
| 87 |
#' @description |
|
| 88 |
#' Method for closing a debug instance from the current debugging |
|
| 89 |
#' stack. If the stopped debug instance is the main level one, the |
|
| 90 |
#' whole debug data is printed out to console. If the stopped debug |
|
| 91 |
#' instance is not the main level one, calculates the execution time |
|
| 92 |
#' of current debug instance and updates the stack data. |
|
| 93 |
#' |
|
| 94 |
close_debug = function() {
|
|
| 95 |
# Pop the last entry |
|
| 96 | 16x |
entry <- self$stack[[length(self$stack)]] |
| 97 | 16x |
self$stack <- self$stack[-length(self$stack)] |
| 98 | ||
| 99 | 16x |
time_diff <- difftime(Sys.time(), entry$start_time, units = "secs") |
| 100 | 16x |
entry$duration <- round(as.numeric(time_diff) * 1000) |
| 101 | ||
| 102 | 16x |
if (length(self$stack) > 0) {
|
| 103 | 11x |
parent <- self$stack[[length(self$stack)]] |
| 104 | 11x |
parent$children[[length(parent$children) + 1]] <- entry |
| 105 | 11x |
self$stack[[length(self$stack)]] <- parent |
| 106 |
} else {
|
|
| 107 | 5x |
self$print_debug_tree(entry) |
| 108 | 5x |
cat("[DEBUG]'\n")
|
| 109 |
} |
|
| 110 |
}, |
|
| 111 | ||
| 112 |
#' @description |
|
| 113 |
#' Recursive method for printing out the current debug stack items and |
|
| 114 |
#' recursively all the item children. This method is called for the whole |
|
| 115 |
#' stack once the topmost debug instance is stopped. |
|
| 116 |
#' |
|
| 117 |
#' @param entry current debug level being processed for printing |
|
| 118 |
#' @param depth current processing depth for printing indentation |
|
| 119 |
#' |
|
| 120 |
print_debug_tree = function(entry, depth = 0) {
|
|
| 121 | 20x |
indent <- paste("[DEBUG]", strrep(" ", depth))
|
| 122 | 20x |
if (isTRUE(entry$is_message)) {
|
| 123 | 4x |
cat(indent, "'", entry$label, "'\n", sep = "") |
| 124 |
} else {
|
|
| 125 | 16x |
execution_time <- paste0("(execution time ", entry$duration, " ms)")
|
| 126 | 16x |
cat(indent, "'", entry$label, "' ", execution_time, "\n", sep = "") |
| 127 | ||
| 128 | 16x |
for (child in entry$children) {
|
| 129 | 15x |
self$print_debug_tree(child, depth + 1) |
| 130 |
} |
|
| 131 |
} |
|
| 132 |
} |
|
| 133 |
) |
|
| 134 |
) |
| 1 |
#' RtfFileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' Specialiced comparator for RTF file comparison. |
|
| 4 |
#' This comparator contains the custom handling for handling only RTF content |
|
| 5 |
#' part for the comparison. |
|
| 6 |
#' |
|
| 7 |
#' @import striprtf |
|
| 8 |
#' |
|
| 9 |
#' @include TxtWithImagesComparator.R |
|
| 10 |
#' |
|
| 11 |
#' @examples |
|
| 12 |
#' |
|
| 13 |
#' # The normal way for creating a comparator would be to call the generic |
|
| 14 |
#' # factory method verifyr2::create_comparator that will automatically create |
|
| 15 |
#' # the correct comparator instance based on the file types. |
|
| 16 |
#' |
|
| 17 |
#' file1 <- 'my_file1.rtf' |
|
| 18 |
#' file2 <- 'my_file2.rtf' |
|
| 19 |
#' comparator <- verifyr2::create_comparator(file1, file2) |
|
| 20 |
#' |
|
| 21 |
#' # If needed, an explicit comparator can be created as well. |
|
| 22 |
#' |
|
| 23 |
#' file1 <- 'my_file1.rtf' |
|
| 24 |
#' file2 <- 'my_file2.rft' |
|
| 25 |
#' comparator <- RtfFileComparator$new(file1, file2) |
|
| 26 |
#' |
|
| 27 |
#' @export |
|
| 28 |
#' |
|
| 29 |
RtfFileComparator <- R6::R6Class( |
|
| 30 |
"RtfFileComparator", |
|
| 31 |
inherit = TxtWithImagesFileComparator, |
|
| 32 |
public = list( |
|
| 33 | ||
| 34 |
#' @description |
|
| 35 |
#' Method for getting the single file contents for the comparison. The |
|
| 36 |
#' method returns the file contents in two separate vectors inside a list. |
|
| 37 |
#' The first vector is the file contents and the second one is the file |
|
| 38 |
#' contents with the rows matching the omit string excluded. This method |
|
| 39 |
#' can be overwritten by more specialized comparator classes. This method |
|
| 40 |
#' is intended to be called only by the comparator classes in the processing |
|
| 41 |
#' and shouldn't be called directly by the user. |
|
| 42 |
#' |
|
| 43 |
#' For RtfComparator, only the RTF file content part is returned for |
|
| 44 |
#' comparison. |
|
| 45 |
#' |
|
| 46 |
#' @param file file for which to get the contents |
|
| 47 |
#' @param config configuration values |
|
| 48 |
#' @param omit string pattern to omit from the comparison |
|
| 49 |
#' |
|
| 50 |
vrf_contents = function(file, config, omit) {
|
|
| 51 | 50x |
self$vrf_open_debug("Rtf::vrf_contents", config)
|
| 52 | ||
| 53 |
# Get the RTF text content |
|
| 54 | 50x |
contents <- striprtf::read_rtf(file = file) |
| 55 | 50x |
result <- self$vrf_contents_inner(contents, config, omit) |
| 56 | ||
| 57 | 50x |
self$vrf_close_debug() |
| 58 | 50x |
result |
| 59 |
}, |
|
| 60 | ||
| 61 |
#' @description |
|
| 62 |
#' "Abstract" method for getting the raw image hex vector array from the |
|
| 63 |
#' given source file. |
|
| 64 |
#' |
|
| 65 |
#' @param file file for which to get the embedded image details |
|
| 66 |
#' @param config configuration values |
|
| 67 |
#' |
|
| 68 |
vrf_images = function(file, config) {
|
|
| 69 | 48x |
self$vrf_open_debug("Rtf::vrf_images", config)
|
| 70 | ||
| 71 |
# return empty list if embedded image processing is disabled |
|
| 72 | 48x |
if ("no" == super$vrf_option_value(config, "rtf.images")) {
|
| 73 | 6x |
self$vrf_close_debug() |
| 74 | 6x |
return(list()) |
| 75 |
} |
|
| 76 | ||
| 77 |
# return empty list if image processing is disabled |
|
| 78 | 42x |
if ("no" == super$vrf_option_value(config, "generic.images")) {
|
| 79 | ! |
self$vrf_close_debug() |
| 80 | ! |
return(list()) |
| 81 |
} |
|
| 82 | ||
| 83 | 42x |
result <- list() |
| 84 | ||
| 85 |
# Read the RTF file embedded images |
|
| 86 | 42x |
rtf_content <- readLines(file, warn = FALSE) |
| 87 | 42x |
rtf_content <- paste(rtf_content, collapse = "\n") |
| 88 | ||
| 89 |
# regexp for finding the png pictures from the raw RTF content. |
|
| 90 | 42x |
pattern <- "\\\\pict\\\\pngblip[^{]+([A-Za-z0-9+/=]+)"
|
| 91 | 42x |
matches <- stringr::str_extract_all(rtf_content, pattern)[[1]] |
| 92 | ||
| 93 | 42x |
if (length(matches) > 0) {
|
| 94 | 8x |
for (index in seq_along(matches)) {
|
| 95 | 8x |
split_parts <- strsplit(matches[index], " ")[[1]] |
| 96 | 8x |
base64_data_with_braces <- split_parts[2] |
| 97 | 8x |
base64_data <- strsplit(base64_data_with_braces, "}")[[1]][1] |
| 98 | ||
| 99 | 8x |
if (!is.na(base64_data) && nchar(base64_data) > 0) {
|
| 100 | 8x |
raw_data <- self$hex2raw(base64_data) |
| 101 | 8x |
result <- c(result, list(raw_data)) |
| 102 |
} |
|
| 103 |
} |
|
| 104 |
} |
|
| 105 | ||
| 106 | 42x |
self$vrf_close_debug() |
| 107 | 42x |
result |
| 108 |
} |
|
| 109 |
) |
|
| 110 |
) |
| 1 |
#' BinaryFileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' Fallback comparator for binary files without any specific definied |
|
| 4 |
#' comparator. |
|
| 5 |
#' |
|
| 6 |
#' @include FileComparator.R |
|
| 7 |
#' |
|
| 8 |
#' @examples |
|
| 9 |
#' |
|
| 10 |
#' # The normal way for creating a comparator would be to call the generic |
|
| 11 |
#' # factory method verifyr2::create_comparator that will automatically create |
|
| 12 |
#' # the correct comparator instance based on the file types. |
|
| 13 |
#' |
|
| 14 |
#' file1 <- 'my_file1.bin' |
|
| 15 |
#' file2 <- 'my_file2.bin' |
|
| 16 |
#' comparator <- verifyr2::create_comparator(file1, file2) |
|
| 17 |
#' |
|
| 18 |
#' # If needed, an explicit comparator can be created as well. |
|
| 19 |
#' |
|
| 20 |
#' file1 <- 'my_file1.bin' |
|
| 21 |
#' file2 <- 'my_file2.bin' |
|
| 22 |
#' comparator <- BinaryFileComparator$new(file1, file2) |
|
| 23 |
#' |
|
| 24 |
#' @export |
|
| 25 |
#' |
|
| 26 |
BinaryFileComparator <- R6::R6Class( |
|
| 27 |
"BinaryFileComparator", |
|
| 28 |
inherit = FileComparator, |
|
| 29 |
public = list( |
|
| 30 | ||
| 31 |
#' @description |
|
| 32 |
#' Method for getting the single file contents for the comparison. This |
|
| 33 |
#' method returns the file contents in two separate vectors inside a list. |
|
| 34 |
#' The first vector is the file contents and the second one is the file |
|
| 35 |
#' contents with the rows matching the omit string excluded. This method |
|
| 36 |
#' can be overwritten by more specialized comparator classes. This method |
|
| 37 |
#' is intended to be called only by the comparator classes in the processing |
|
| 38 |
#' and shouldn not be called directly by the user. |
|
| 39 |
#' |
|
| 40 |
#' @param file file for which to get the contents |
|
| 41 |
#' @param config configuration values |
|
| 42 |
#' @param omit string pattern to omit from the comparison |
|
| 43 |
#' |
|
| 44 |
vrf_contents = function(file, config, omit) {
|
|
| 45 | 48x |
self$vrf_open_debug("Binary::vrf_contents", config)
|
| 46 | ||
| 47 | 48x |
contents <- readLines(file, warn = FALSE) |
| 48 | 48x |
result <- self$vrf_contents_inner(contents, config, omit) |
| 49 | ||
| 50 | 48x |
self$vrf_close_debug() |
| 51 | 48x |
result |
| 52 |
}, |
|
| 53 | ||
| 54 |
#' @description |
|
| 55 |
#' Method for getting the inner part for the file contents query. The method |
|
| 56 |
#' returns the file contents in two separate vectors inside a list. The |
|
| 57 |
#' first vector is the file contents and the second one is the file contents |
|
| 58 |
#' with the rows matching the omit string excluded. This method can be |
|
| 59 |
#' overwritten by more specialized comparator classes. This method is |
|
| 60 |
#' intended to be called only by the comparator classes in the processing |
|
| 61 |
#' and shouldn not be called directly by the user. |
|
| 62 |
#' |
|
| 63 |
#' @param contents file contents |
|
| 64 |
#' @param config configuration values |
|
| 65 |
#' @param omit string pattern to omit from the comparison |
|
| 66 |
#' |
|
| 67 |
vrf_contents_inner = function(contents, config, omit) {
|
|
| 68 | 6x |
self$vrf_add_debug("Binary::vrf_contents_inner")
|
| 69 | 6x |
list(contents, contents) |
| 70 |
}, |
|
| 71 | ||
| 72 |
#' @description |
|
| 73 |
#' Method for comparing the inner part for the details query. The method |
|
| 74 |
#' returns the file contents in two separate vectors inside a list. The |
|
| 75 |
#' first vector is the file contents and the second one is the file contents |
|
| 76 |
#' with the rows matching the omit string excluded. This method can be |
|
| 77 |
#' overwritten by more specialized comparator classes. This method is |
|
| 78 |
#' intended to be called only by the comparator classes in the processing |
|
| 79 |
#' and shouldn not be called directly by the user. |
|
| 80 |
#' |
|
| 81 |
#' @param config configuration values |
|
| 82 |
#' @param omit string pattern to omit from the comparison |
|
| 83 |
#' |
|
| 84 |
vrf_summary_inner = function(config, omit) {
|
|
| 85 | 6x |
self$vrf_open_debug("Binary::vrf_summary_inner", config)
|
| 86 | ||
| 87 | 6x |
file_info1 <- file.info(self$file1) |
| 88 | 6x |
file_info2 <- file.info(self$file2) |
| 89 | ||
| 90 | 6x |
if (file_info1$size != file_info2$size) {
|
| 91 | 3x |
self$vrf_close_debug() |
| 92 | 3x |
return("Different file sizes for compared files.")
|
| 93 |
} |
|
| 94 | ||
| 95 | 3x |
file1_contents_list <- self$file1_contents_list |
| 96 | 3x |
file2_contents_list <- self$file2_contents_list |
| 97 | ||
| 98 | 3x |
if (is.null(file1_contents_list)) {
|
| 99 | 3x |
file1_contents_list <- self$vrf_contents(self$file1, config, omit) |
| 100 | 3x |
self$file1_contents_list <- file1_contents_list |
| 101 |
} |
|
| 102 | ||
| 103 | 3x |
if (is.null(file2_contents_list)) {
|
| 104 | 3x |
file2_contents_list <- self$vrf_contents(self$file2, config, omit) |
| 105 | 3x |
self$file2_contents_list <- file2_contents_list |
| 106 |
} |
|
| 107 | ||
| 108 | 3x |
file1_contents_omit <- file1_contents_list[[2]] |
| 109 | 3x |
file2_contents_omit <- file2_contents_list[[2]] |
| 110 | ||
| 111 | 3x |
if (!identical(file1_contents_omit, file2_contents_omit)) {
|
| 112 | 1x |
self$vrf_close_debug() |
| 113 | 1x |
return("Different content in compared files.")
|
| 114 |
} |
|
| 115 | ||
| 116 | 2x |
self$vrf_close_debug() |
| 117 | 2x |
"No differences." |
| 118 |
}, |
|
| 119 | ||
| 120 |
#' @description |
|
| 121 |
#' Method for comparing the inner part for the details query. This method |
|
| 122 |
#' can be overwritten by more specialized comparator classes. This method is |
|
| 123 |
#' intended to be called only by the comparator classes in the processing |
|
| 124 |
#' and shouldn't be called directly by the user. |
|
| 125 |
#' |
|
| 126 |
#' @param config configuration values |
|
| 127 |
#' @param omit string pattern to omit from the comparison |
|
| 128 |
#' |
|
| 129 |
vrf_details_inner = function(config, omit) {
|
|
| 130 | ! |
self$vrf_add_debug("Binary::vrf_details_inner")
|
| 131 | ! |
result <- list( |
| 132 | ! |
type = "text", |
| 133 | ! |
contents = "Binary file without applicable comparator." |
| 134 |
) |
|
| 135 | ! |
list(result) |
| 136 |
} |
|
| 137 |
) |
|
| 138 |
) |
| 1 |
#' TxtWithImageFileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' "Abstract" comparator for txt based comparator classes that can additionally |
|
| 4 |
#' contain embedded images. This abstraction level contains generic logic for |
|
| 5 |
#' handling embedded images and storing the related data. |
|
| 6 |
#' |
|
| 7 |
#' @import stringr |
|
| 8 |
#' |
|
| 9 |
#' @include TxtFileComparator.R |
|
| 10 |
#' |
|
| 11 |
#' @field file1_images_raw local property for storing image1 raw data |
|
| 12 |
#' @field file2_images_raw local property for storing image2 raw data |
|
| 13 |
#' |
|
| 14 | ||
| 15 |
# Disable cyclomatic complexity lint for the R6 class definition as lintr |
|
| 16 |
# considers the whole class definition as a single function. |
|
| 17 |
# |
|
| 18 |
# nolint start: cyclocomp_linter |
|
| 19 |
TxtWithImagesFileComparator <- R6::R6Class( |
|
| 20 |
"TxtWithImagesFileComparator", |
|
| 21 |
inherit = TxtFileComparator, |
|
| 22 |
public = list( |
|
| 23 |
file1_images_raw = NULL, |
|
| 24 |
file2_images_raw = NULL, |
|
| 25 | ||
| 26 |
#' @description |
|
| 27 |
#' Initialize a TxtWithImagesFileComparator instance |
|
| 28 |
#' |
|
| 29 |
#' @param file1 First file to compare. |
|
| 30 |
#' @param file2 Second file to compare. |
|
| 31 |
#' |
|
| 32 |
initialize = function(file1 = NULL, file2 = NULL) {
|
|
| 33 | 25x |
super$initialize(file1, file2) |
| 34 |
}, |
|
| 35 | ||
| 36 |
#' @description |
|
| 37 |
#' Method for comparing the inner part for the details query. This method |
|
| 38 |
#' can be overwritten by more specialized comparator classes. This method is |
|
| 39 |
#' intended to be called only by the comparator classes in the processing |
|
| 40 |
#' and shouldn't be called directly by the user. |
|
| 41 |
#' |
|
| 42 |
#' @param config configuration values |
|
| 43 |
#' @param omit string pattern to omit from the comparison |
|
| 44 |
#' |
|
| 45 |
vrf_summary_inner = function(config, omit) {
|
|
| 46 | 20x |
self$vrf_open_debug("TxtWithImages::vrf_summary_inner" , config)
|
| 47 | ||
| 48 | 20x |
result <- super$vrf_summary_inner(config, omit) |
| 49 | ||
| 50 | 20x |
if ("no" != super$vrf_option_value(config, "generic.images")) {
|
| 51 | 19x |
file1_contents_list <- self$file1_contents_list |
| 52 | 19x |
file2_contents_list <- self$file2_contents_list |
| 53 | ||
| 54 | 19x |
if (is.null(self$file1_images_raw)) {
|
| 55 | 19x |
self$file1_images_raw <- self$vrf_images(self$file1, config) |
| 56 |
} |
|
| 57 | ||
| 58 | 19x |
if (is.null(self$file2_images_raw)) {
|
| 59 | 19x |
self$file2_images_raw <- self$vrf_images(self$file2, config) |
| 60 |
} |
|
| 61 | ||
| 62 |
# Generate additional summary string based on embedded image differences |
|
| 63 |
# if applicable. |
|
| 64 | 19x |
if (0 != length(self$file1_images_raw) && 0 != length(self$file2_images_raw)) {
|
| 65 | 2x |
result_images <- "No differences in embedded images." |
| 66 |
|
|
| 67 | 2x |
if (length(self$file1_images_raw) != length(self$file2_images_raw)) {
|
| 68 |
# Number of found embedded images differs between the files. |
|
| 69 | ! |
result_images <- "Different amount of embedded images." |
| 70 |
} else {
|
|
| 71 |
# Number of found embedded images is the same; calculate how many of |
|
| 72 |
# the embedded images has changed (based on raw file data) compared to |
|
| 73 |
# total count. |
|
| 74 | 2x |
matches <- 0 |
| 75 | 2x |
total <- length(self$file1_images_raw) |
| 76 | ||
| 77 | 2x |
for (index in seq_along(self$file1_images_raw)) {
|
| 78 | 2x |
if (identical(self$file1_images_raw[[index]], self$file2_images_raw[[index]])) {
|
| 79 | 1x |
matches <- matches + 1 |
| 80 |
} |
|
| 81 |
} |
|
| 82 | ||
| 83 | 2x |
if (matches != length(self$file1_images_raw)) {
|
| 84 | 1x |
result_images <- paste0(total - matches, "/", total, " embedded images have differences.") |
| 85 |
} |
|
| 86 |
} |
|
| 87 | 2x |
result <- paste0(result, " ", result_images) |
| 88 |
} |
|
| 89 |
} |
|
| 90 | ||
| 91 | 20x |
self$vrf_close_debug() |
| 92 | 20x |
return(result) |
| 93 |
}, |
|
| 94 | ||
| 95 |
#' @description |
|
| 96 |
#' Method for comparing the inner part for the details query. This method |
|
| 97 |
#' can be overwritten by more specialized comparator classes. This method is |
|
| 98 |
#' intended to be called only by the comparator classes in the processing |
|
| 99 |
#' and shouldn't be called directly by the user. |
|
| 100 |
#' |
|
| 101 |
#' @param config configuration values |
|
| 102 |
#' @param omit string pattern to omit from the comparison |
|
| 103 |
#' |
|
| 104 |
vrf_details_inner = function(config, omit) {
|
|
| 105 | 5x |
self$vrf_open_debug("TxtWithImages::vrf_details_inner" , config)
|
| 106 | ||
| 107 | 5x |
result <- super$vrf_details_inner(config, omit) |
| 108 | ||
| 109 | 5x |
if ("no" != super$vrf_option_value(config, "generic.images")) {
|
| 110 | 5x |
file1_contents_list <- self$file1_contents_list |
| 111 | 5x |
file2_contents_list <- self$file2_contents_list |
| 112 | ||
| 113 | 5x |
if (is.null(self$file1_images_raw)) {
|
| 114 | 5x |
self$file1_images_raw <- self$vrf_images(self$file1, config) |
| 115 |
} |
|
| 116 | ||
| 117 | 5x |
if (is.null(self$file2_images_raw)) {
|
| 118 | 5x |
self$file2_images_raw <- self$vrf_images(self$file2, config) |
| 119 |
} |
|
| 120 | ||
| 121 |
# Append the possible extended images into the result list if applicable. |
|
| 122 |
# List of images is included in the fileX_contents_lists if found from |
|
| 123 |
# content getter. |
|
| 124 | 5x |
if (0 != length(self$file1_images_raw) && 0 != length(self$file2_images_raw)) {
|
| 125 | ||
| 126 |
# Only display the differences if there is the same amount of images |
|
| 127 |
# found from the compared files. Otherwise it would require additional |
|
| 128 |
# logic to decide which files should be compared with each others (which |
|
| 129 |
# is something that could be developed further with size comparisons). |
|
| 130 | 2x |
if (length(self$file1_images_raw) == length(self$file2_images_raw)) {
|
| 131 | 2x |
for (index in seq_along(self$file1_images_raw)) {
|
| 132 |
# Manually create a ImgFileComparator instance for every embedded |
|
| 133 |
# image and call the details comparison based on existing bin data. |
|
| 134 | 2x |
comparator <- ImgFileComparator$new( |
| 135 | 2x |
NULL, |
| 136 | 2x |
NULL, |
| 137 | 2x |
self$file1_images_raw[[index]], |
| 138 | 2x |
self$file2_images_raw[[index]] |
| 139 |
) |
|
| 140 | 2x |
result <- append(result, comparator$vrf_details_inner(config, omit)) |
| 141 |
} |
|
| 142 |
} |
|
| 143 |
} |
|
| 144 |
} |
|
| 145 | ||
| 146 | 5x |
self$vrf_close_debug() |
| 147 | 5x |
return(result) |
| 148 |
}, |
|
| 149 | ||
| 150 |
#' @description |
|
| 151 |
#' "Abstract" method for getting the raw image hex vector array from the |
|
| 152 |
#' given source file. |
|
| 153 |
#' |
|
| 154 |
#' @param file file for which to get the embedded image details |
|
| 155 |
#' |
|
| 156 |
vrf_images = function(file) {
|
|
| 157 | ! |
stop("vrf_images must be implemented in a subclass.")
|
| 158 |
}, |
|
| 159 | ||
| 160 |
#' @description |
|
| 161 |
#' Internal helper method for converting a hex string to raw vector. |
|
| 162 |
#' |
|
| 163 |
#' @param hex_string hexadecimal string to be converted to raw vector |
|
| 164 |
#' |
|
| 165 |
hex2raw = function(hex_string) {
|
|
| 166 |
# Remove non-hex characters |
|
| 167 | 8x |
hex_string <- gsub("[^0-9a-fA-F]", "", hex_string)
|
| 168 | ||
| 169 |
# check that the input string is a hex string |
|
| 170 | 8x |
if (nchar(hex_string) %% 2 != 0 || nchar(hex_string) < 2) {
|
| 171 | ! |
stop("input string isn't a hex string")
|
| 172 |
} |
|
| 173 | ||
| 174 |
# Generate start and end indices |
|
| 175 | 8x |
start_indices <- seq(1, nchar(hex_string), 2) |
| 176 | 8x |
end_indices <- seq(2, nchar(hex_string), 2) |
| 177 | ||
| 178 |
# Extract byte-sized chunks |
|
| 179 | 8x |
bytes <- substring(hex_string, start_indices, end_indices) |
| 180 | ||
| 181 | 8x |
as.raw(as.hexmode(bytes)) |
| 182 |
} |
|
| 183 |
) |
|
| 184 |
) |
|
| 185 |
# nolint end: cyclocomp_linter |
| 1 |
#' List files that exist in two folders |
|
| 2 |
#' |
|
| 3 |
#' \code{list_folder_files} List files that exist in two folders with a specific
|
|
| 4 |
#' name pattern |
|
| 5 |
#' |
|
| 6 |
#' @param folder1 character, giving the the full file path and name of the |
|
| 7 |
#' folder where first files are stored (required) |
|
| 8 |
#' @param folder2 character, giving the the full file path and name of the |
|
| 9 |
#' folder where second new files are stored (required) |
|
| 10 |
#' @param pattern character, limit the files to be listed to contain a |
|
| 11 |
#' specific pattern (optional) |
|
| 12 |
#' |
|
| 13 |
#' @return Returns a tibble, \code{selected_files} with 2 columns \code{file1},
|
|
| 14 |
#' \code{file2}
|
|
| 15 |
#' |
|
| 16 |
#' @examples |
|
| 17 |
#' |
|
| 18 |
#' folder1 <- paste0(fs::path_package("/extdata/base_files/",
|
|
| 19 |
#' package = "verifyr2")) |
|
| 20 |
#' |
|
| 21 |
#' folder2 <- paste0(fs::path_package("/extdata/compare_files/",
|
|
| 22 |
#' package = "verifyr2")) |
|
| 23 |
#' |
|
| 24 |
#' verifyr2::list_folder_files(folder1, folder2, "base") |
|
| 25 |
#' |
|
| 26 |
#' @export |
|
| 27 | ||
| 28 |
list_folder_files <- function(folder1, folder2, pattern = NULL) {
|
|
| 29 | ||
| 30 |
## do the comparison only if both of the folders exist |
|
| 31 | 3x |
if (file.exists(folder1) && file.exists(folder2)) {
|
| 32 | ||
| 33 | 1x |
folder1_info <- folder_info(folder1, "file1", pattern) |
| 34 | 1x |
folder2_info <- folder_info(folder2, "file2", pattern) |
| 35 | ||
| 36 | 1x |
selected_files <- dplyr::full_join(folder1_info, |
| 37 | 1x |
folder2_info, |
| 38 | 1x |
by = "file") %>% |
| 39 | 1x |
dplyr::arrange(file) %>% |
| 40 | 1x |
dplyr::select("file1", "file2")
|
| 41 | ||
| 42 | 1x |
return(selected_files) |
| 43 |
} |
|
| 44 | ||
| 45 | 2x |
NULL |
| 46 |
} |
|
| 47 | ||
| 48 |
folder_info <- function(folder, column_name, pattern) {
|
|
| 49 | 2x |
files <- list.files(path = folder, pattern = pattern) |
| 50 | 2x |
paths <- list.files(path = folder, pattern = pattern, full.names = TRUE) |
| 51 | 2x |
data <- tibble::tibble(file = files) |
| 52 | 2x |
data[[column_name]] <- paths |
| 53 | ||
| 54 | 2x |
data |
| 55 |
} |
| 1 |
#' FileComparatorFactory.R |
|
| 2 |
#' |
|
| 3 |
#' Factory method for creating comparator instance based on the given two files. |
|
| 4 |
#' |
|
| 5 |
#' @param file1 first file to compare |
|
| 6 |
#' @param file2 second file to compare |
|
| 7 |
#' |
|
| 8 |
#' @examples |
|
| 9 |
#' |
|
| 10 |
#' # instantiating the compared files |
|
| 11 |
#' file1 <- "file1.rtf" |
|
| 12 |
#' file2 <- "file2.rtf" |
|
| 13 |
#' |
|
| 14 |
#' # instantiating the configuration |
|
| 15 |
#' config <- Config$new() |
|
| 16 |
#' |
|
| 17 |
#' # instantiating a new comparator instance for every comparison: |
|
| 18 |
#' comparator <- verifyr2::create_comparator(file1, file2) |
|
| 19 |
#' |
|
| 20 |
#' # calling the summary and details comparison methods |
|
| 21 |
#' comparator$vrf_summary(config = config) |
|
| 22 |
#' comparator$vrf_details(config = config) |
|
| 23 |
#' |
|
| 24 |
#' @include BinaryFileComparator.R |
|
| 25 |
#' @include TxtFileComparator.R |
|
| 26 |
#' |
|
| 27 |
#' @export |
|
| 28 |
#' |
|
| 29 |
create_comparator <- function(file1, file2) {
|
|
| 30 | 94x |
if (!file.exists(file1) || !file.exists(file2)) {
|
| 31 | 20x |
return(BinaryFileComparator$new(file1 = file1, file2 = file2)) |
| 32 |
} |
|
| 33 | ||
| 34 | 74x |
file_extension <- tools::toTitleCase(tools::file_ext(file1)) |
| 35 | ||
| 36 | 74x |
if (file_extension %in% list("Jpg", "Jpeg", "Png")) {
|
| 37 | 5x |
file_extension <- "Img" |
| 38 |
} |
|
| 39 | ||
| 40 |
# construct the comparator name |
|
| 41 | 74x |
comparator_name <- paste0(file_extension, "FileComparator") |
| 42 | ||
| 43 | 74x |
if (exists(comparator_name, envir = .GlobalEnv)) {
|
| 44 | 63x |
class_def <- get(comparator_name, envir = .GlobalEnv) |
| 45 | ||
| 46 |
# return the specific class instance if the class exists |
|
| 47 | 63x |
if (inherits(class_def, "R6ClassGenerator")) {
|
| 48 | 63x |
return(do.call(class_def$new, list(file1 = file1, file2 = file2))) |
| 49 |
} |
|
| 50 |
} |
|
| 51 | ||
| 52 |
# generic comparator class used based on the file contents (text/binary). |
|
| 53 | 11x |
mime_type <- mime::guess_type(file1) |
| 54 | ||
| 55 | 11x |
text_like <- c( |
| 56 | 11x |
"application/json", |
| 57 | 11x |
"application/xml", |
| 58 | 11x |
"application/javascript", |
| 59 | 11x |
"application/x-yaml", |
| 60 | 11x |
"application/sql", |
| 61 | 11x |
"application/x-httpd-php", |
| 62 | 11x |
"application/x-sh", |
| 63 | 11x |
"application/csv", |
| 64 | 11x |
"application/x-tex", |
| 65 | 11x |
"application/x-markdown" |
| 66 |
) |
|
| 67 | ||
| 68 |
# additional types that the mime::guess_type mapping doesn't handle correctly |
|
| 69 | 11x |
additional_text_types <- c( |
| 70 | 11x |
"Lst", |
| 71 | 11x |
"Sas" |
| 72 |
) |
|
| 73 | ||
| 74 | 11x |
txt_type <- startsWith(mime_type, "text/") |
| 75 | 11x |
fix_type <- mime_type %in% text_like |
| 76 | 11x |
add_type <- file_extension %in% additional_text_types |
| 77 | ||
| 78 | 11x |
if (txt_type || fix_type || add_type) {
|
| 79 | 7x |
TxtFileComparator$new(file1 = file1, file2 = file2) |
| 80 |
} else {
|
|
| 81 | 4x |
BinaryFileComparator$new(file1 = file1, file2 = file2) |
| 82 |
} |
|
| 83 |
} |
|
| 84 | ||
| 85 |
is_r6_class <- function(class_name) {
|
|
| 86 | ! |
obj <- try(get(class_name, envir = .GlobalEnv), silent = TRUE) |
| 87 | ! |
inherits(obj, "R6ClassGenerator") |
| 88 |
} |
| 1 |
#' Call for shiny example where the user can test verifyr2 package functions |
|
| 2 |
#' |
|
| 3 |
#' \code{verifyr2::run_example} returns simple Shiny App where user can see how
|
|
| 4 |
#' the verifyr2 functions work |
|
| 5 |
#' |
|
| 6 |
#' @export |
|
| 7 |
#' |
|
| 8 |
#' @param debug option to override debug configuration (TRUE only) |
|
| 9 |
#' |
|
| 10 |
run_example <- function(debug = FALSE) {
|
|
| 11 | ! |
appDir <- system.file("shiny_examples", "app", package = "verifyr2")
|
| 12 | ||
| 13 | ! |
if (appDir == "") {
|
| 14 | ! |
stop("Could not find example directory. Try re-installing `verifyr2`.",
|
| 15 | ! |
call. = FALSE) |
| 16 |
} |
|
| 17 | ||
| 18 | ! |
options(verifyr2.debug = debug) |
| 19 | ! |
shiny::runApp(appDir, display.mode = "normal") |
| 20 |
} |
| 1 |
#' ImgFileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' Specialiced comparator for image file (jpg, jpef, png) comparison. |
|
| 4 |
#' This comparator contains the custom handling for handling only img content |
|
| 5 |
#' part for the comparison. |
|
| 6 |
#' |
|
| 7 |
#' @import base64enc |
|
| 8 |
#' |
|
| 9 |
#' @include BinaryFileComparator.R |
|
| 10 |
#' |
|
| 11 |
#' @field image1_raw extracted raw data for image1. |
|
| 12 |
#' @field image2_raw extracted raw data for image2. |
|
| 13 |
#' |
|
| 14 |
#' @examples |
|
| 15 |
#' |
|
| 16 |
#' # The normal way for creating a comparator would be to call the generic |
|
| 17 |
#' # factory method verifyr2::create_comparator that will automatically create |
|
| 18 |
#' # the correct comparator instance based on the file types. |
|
| 19 |
#' |
|
| 20 |
#' file1 <- 'my_file1.jpg' |
|
| 21 |
#' file2 <- 'my_file2.jpg' |
|
| 22 |
#' comparator <- verifyr2::create_comparator(file1, file2) |
|
| 23 |
#' |
|
| 24 |
#' # If needed, an explicit comparator can be created as well. |
|
| 25 |
#' |
|
| 26 |
#' file1 <- 'my_file1.png' |
|
| 27 |
#' file2 <- 'my_file2.png' |
|
| 28 |
#' comparator <- ImgFileComparator$new(file1, file2) |
|
| 29 |
#' |
|
| 30 |
#' # This comparator has also second explicit creation method that is used |
|
| 31 |
#' # by the library for processing embedded image contents. |
|
| 32 |
#' |
|
| 33 |
#' image1_raw <- 'raw hex vector data' |
|
| 34 |
#' image2_raw <- 'raw hex vector data' |
|
| 35 |
#' comparator <- ImgFileComparator$new(NULL, NULL, image1_raw, image2_raw) |
|
| 36 |
#' |
|
| 37 |
#' @export |
|
| 38 |
#' |
|
| 39 |
ImgFileComparator <- R6::R6Class( |
|
| 40 |
"ImgFileComparator", |
|
| 41 |
inherit = BinaryFileComparator, |
|
| 42 |
public = list( |
|
| 43 |
image1_raw = NULL, |
|
| 44 |
image2_raw = NULL, |
|
| 45 | ||
| 46 |
#' @description |
|
| 47 |
#' Initialize a ImgFileComparator instance |
|
| 48 |
#' |
|
| 49 |
#' @param file1 First file to compare. |
|
| 50 |
#' @param file2 Second file to compare. |
|
| 51 |
#' @param raw1 First image in raw format to compare. |
|
| 52 |
#' @param raw2 Second image in raw format to compare. |
|
| 53 |
#' |
|
| 54 |
initialize = function( |
|
| 55 |
file1 = NULL, |
|
| 56 |
file2 = NULL, |
|
| 57 |
raw1 = NULL, |
|
| 58 |
raw2 = NULL |
|
| 59 |
) {
|
|
| 60 | 7x |
self$image1_raw <- raw1 |
| 61 | 7x |
self$image2_raw <- raw2 |
| 62 | 7x |
super$initialize(file1, file2) |
| 63 |
}, |
|
| 64 | ||
| 65 |
#' @description |
|
| 66 |
#' Method for comparing the inner part for the details query. This method |
|
| 67 |
#' can be overwritten by more specialized comparator classes. This method is |
|
| 68 |
#' intended to be called only by the comparator classes in the processing |
|
| 69 |
#' and shouldn't be called directly by the user. |
|
| 70 |
#' |
|
| 71 |
#' @param config configuration values |
|
| 72 |
#' @param omit string pattern to omit from the comparison |
|
| 73 |
#' |
|
| 74 |
vrf_details_inner = function(config, omit) {
|
|
| 75 | 4x |
self$vrf_open_debug("Img::vrf_details_inner", config)
|
| 76 | ||
| 77 | 4x |
if ("no" == super$vrf_option_value(config, "generic.images")) {
|
| 78 | ! |
result <- list( |
| 79 | ! |
list( |
| 80 | ! |
type = "text", |
| 81 | ! |
contents = "Image comparison disabled; no comparison done." |
| 82 |
) |
|
| 83 |
) |
|
| 84 | ||
| 85 | ! |
self$vrf_close_debug() |
| 86 | ! |
return(result) |
| 87 |
} |
|
| 88 | ||
| 89 | 4x |
if (is.null(self$image1_raw) || is.null(self$image2_raw)) {
|
| 90 | 2x |
result <- self$vrf_details_inner_from_files(config) |
| 91 | ||
| 92 | 2x |
self$vrf_close_debug() |
| 93 | 2x |
return(result) |
| 94 |
} |
|
| 95 | ||
| 96 | 2x |
result <- self$vrf_details_inner_from_raw(config) |
| 97 | ||
| 98 | 2x |
self$vrf_close_debug() |
| 99 | 2x |
result |
| 100 |
}, |
|
| 101 | ||
| 102 |
#' @description |
|
| 103 |
#' Internal method for comparing the earlier populated raw image contents in |
|
| 104 |
#' details and generating the difference highlight image in case differences |
|
| 105 |
#' are found. |
|
| 106 |
#' |
|
| 107 |
#' @param config configuration values |
|
| 108 |
#* |
|
| 109 |
vrf_details_inner_from_raw = function(config) {
|
|
| 110 | 4x |
self$vrf_open_debug("Img::vrf_details_inner_from_raw", config)
|
| 111 | ||
| 112 | 4x |
png_prefix <- "data:image/png;base64," |
| 113 | 4x |
image1_raw <- self$image1_raw |
| 114 | 4x |
image2_raw <- self$image2_raw |
| 115 | 4x |
image3_base64 <- NULL |
| 116 | ||
| 117 | 4x |
if (!identical(image1_raw, image2_raw)) {
|
| 118 | 3x |
image1 <- magick::image_read(image1_raw) |
| 119 | 3x |
image2 <- magick::image_read(image2_raw) |
| 120 | ||
| 121 | 3x |
diff <- magick::image_compare(image1, image2, metric = "AE") |
| 122 | 3x |
highlight <- magick::image_composite(image1, diff, operator = "atop") |
| 123 | ||
| 124 | 3x |
image3_raw <- magick::image_write(highlight, format = "png") |
| 125 | 3x |
image3_base64 <- paste0(png_prefix, base64enc::base64encode(image3_raw)) |
| 126 |
} |
|
| 127 | ||
| 128 | 4x |
image1_base64 <- paste0(png_prefix, base64enc::base64encode(image1_raw)) |
| 129 | 4x |
image2_base64 <- paste0(png_prefix, base64enc::base64encode(image2_raw)) |
| 130 | ||
| 131 | 4x |
result <- list( |
| 132 | 4x |
type = "image", |
| 133 | 4x |
contents = list( |
| 134 | 4x |
image1 = image1_base64, |
| 135 | 4x |
image2 = image2_base64, |
| 136 | 4x |
image3 = image3_base64 |
| 137 |
) |
|
| 138 |
) |
|
| 139 | ||
| 140 | 4x |
self$vrf_close_debug() |
| 141 | 4x |
list(result) |
| 142 |
}, |
|
| 143 | ||
| 144 |
#' @description |
|
| 145 |
#' Method for comparing the inner part for the details query with the file |
|
| 146 |
#' names as the base arguments. This is a part of a group of image |
|
| 147 |
#' processing functions that work with different image abstractions (file, |
|
| 148 |
#' image, raw image). These methods are intended to improve the performance |
|
| 149 |
#' so that best suiting method version can be used depending on what data is |
|
| 150 |
#' available from the earlier method calls to the same comparator instance. |
|
| 151 |
#' |
|
| 152 |
#' @param config configuration values |
|
| 153 |
#' |
|
| 154 |
vrf_details_inner_from_files = function(config) {
|
|
| 155 | 2x |
self$vrf_open_debug("Img::vrf_details_inner_from_files", config)
|
| 156 | ||
| 157 | 2x |
file1_size <- file.info(self$file1)$size |
| 158 | 2x |
self$image1_raw <- readBin(self$file1, what = "raw", n = file1_size) |
| 159 | ||
| 160 | 2x |
file2_size <- file.info(self$file2)$size |
| 161 | 2x |
self$image2_raw <- readBin(self$file2, what = "raw", n = file2_size) |
| 162 | ||
| 163 | 2x |
result <- self$vrf_details_inner_from_raw(config) |
| 164 | ||
| 165 | 2x |
self$vrf_close_debug() |
| 166 | 2x |
result |
| 167 |
}, |
|
| 168 | ||
| 169 |
#' @description |
|
| 170 |
#' Inherited method for indicating whether detailed comparison is available |
|
| 171 |
#' with the current comparator. Returns an empty string if the comparator is |
|
| 172 |
#' is supported, otherwise a string that will be concatenated with the |
|
| 173 |
#' summary string. |
|
| 174 |
#' |
|
| 175 |
#' @param config configuration values |
|
| 176 |
#' |
|
| 177 |
vrf_details_supported = function(config) {
|
|
| 178 | 5x |
if ("no" == super$vrf_option_value(config, "generic.images")) {
|
| 179 | 1x |
return("Image details comparison disabled.")
|
| 180 |
} |
|
| 181 |
"" |
|
| 182 |
} |
|
| 183 |
) |
|
| 184 |
) |
| 1 |
#' PdfFileComparator.R |
|
| 2 |
#' |
|
| 3 |
#' Specialiced comparator for PDF file comparison. |
|
| 4 |
#' This comparator contains the custom handling for handling only PDF content |
|
| 5 |
#' part for the comparison. |
|
| 6 |
#' |
|
| 7 |
#' @include BinaryFileComparator.R |
|
| 8 |
#' @include TxtFileComparator.R |
|
| 9 |
#' |
|
| 10 |
#' @examples |
|
| 11 |
#' |
|
| 12 |
#' # The normal way for creating a comparator would be to call the generic |
|
| 13 |
#' # factory method verifyr2::create_comparator that will automatically create |
|
| 14 |
#' # the correct comparator instance based on the file types. |
|
| 15 |
#' |
|
| 16 |
#' file1 <- 'my_file1.pdf' |
|
| 17 |
#' file2 <- 'my_file2.pdf' |
|
| 18 |
#' comparator <- verifyr2::create_comparator(file1, file2) |
|
| 19 |
#' |
|
| 20 |
#' # If needed, an explicit comparator can be created as well. |
|
| 21 |
#' |
|
| 22 |
#' file1 <- 'my_file1.pdf' |
|
| 23 |
#' file2 <- 'my_file2.pdf' |
|
| 24 |
#' comparator <- PdfFileComparator$new(file1, file2) |
|
| 25 |
#' |
|
| 26 |
#' @export |
|
| 27 |
#' |
|
| 28 |
PdfFileComparator <- R6::R6Class( |
|
| 29 |
"PdfFileComparator", |
|
| 30 |
inherit = TxtFileComparator, |
|
| 31 |
public = list( |
|
| 32 | ||
| 33 |
#' @description |
|
| 34 |
#' Method for getting the single file contents for the comparison. The |
|
| 35 |
#' method returns the file contents in two separate vectors inside a list. |
|
| 36 |
#' The first vector is the file contents and the second one is the file |
|
| 37 |
#' contents with the rows matching the omit string excluded. This method |
|
| 38 |
#' can be overwritten by more specialized comparator classes. This method |
|
| 39 |
#' is intended to be called only by the comparator classes in the processing |
|
| 40 |
#' and shouldn't be called directly by the user. |
|
| 41 |
#' |
|
| 42 |
#' @param file file for which to get the contents |
|
| 43 |
#' @param config configuration values |
|
| 44 |
#' @param omit string pattern to omit from the comparison |
|
| 45 |
#' |
|
| 46 |
vrf_contents = function(file, config, omit) {
|
|
| 47 | 40x |
self$vrf_open_debug("Pdf::vrf_contents", config)
|
| 48 | ||
| 49 | 40x |
if ("no" == super$vrf_option_value(config, "pdf.details")) {
|
| 50 | 2x |
result <- super$vrf_contents(file, config, omit) |
| 51 | ||
| 52 | 2x |
self$vrf_close_debug() |
| 53 | 2x |
return(result) |
| 54 |
} |
|
| 55 | ||
| 56 | 38x |
content <- pdftools::pdf_text(file) |
| 57 | 38x |
content <- paste(content, collapse = "") |
| 58 | 38x |
content <- strsplit(content, "\n")[[1]] |
| 59 | ||
| 60 | 38x |
result <- self$vrf_contents_inner(content, config, omit) |
| 61 | ||
| 62 | 38x |
self$vrf_close_debug() |
| 63 | 38x |
result |
| 64 |
}, |
|
| 65 | ||
| 66 |
#' @description |
|
| 67 |
#' Inherited method for indicating whether detailed comparison is available |
|
| 68 |
#' with the current comparator. Returns an empty string if the comparator is |
|
| 69 |
#' is supported, otherwise a string that will be concatenated with the |
|
| 70 |
#' summary string. |
|
| 71 |
#' |
|
| 72 |
#' @param config configuration values |
|
| 73 |
#' |
|
| 74 |
vrf_details_supported = function(config) {
|
|
| 75 | 20x |
if ("no" == super$vrf_option_value(config, "pdf.details")) {
|
| 76 | 1x |
return("Pdf details comparison disabled.")
|
| 77 |
} |
|
| 78 | 19x |
super$vrf_details_supported(config) |
| 79 |
} |
|
| 80 |
) |
|
| 81 |
) |
| 1 | ||
| 2 |
merge_values <- function(defaults, overrides) {
|
|
| 3 | 30x |
result <- list() |
| 4 | ||
| 5 | 30x |
for (name in names(defaults)) {
|
| 6 | 66x |
def <- defaults[[name]] |
| 7 | 66x |
over <- overrides[[name]] |
| 8 | ||
| 9 | 66x |
has_defaults <- "default" %in% names(def) |
| 10 | 66x |
has_options <- "options" %in% names(def) |
| 11 | ||
| 12 | 66x |
if (is.list(def) && has_defaults && has_options) {
|
| 13 | 15x |
if (!is.null(over) && over %in% def$options) {
|
| 14 | 8x |
result[[name]] <- over |
| 15 |
} else {
|
|
| 16 | 7x |
result[[name]] <- def$default |
| 17 |
} |
|
| 18 | 51x |
} else if (is.list(def)) {
|
| 19 | 5x |
if (!is.list(over)) over <- list() |
| 20 | 24x |
result[[name]] <- merge_values(def, over) |
| 21 |
} else {
|
|
| 22 | 27x |
result[[name]] <- if (!is.null(over)) over else def |
| 23 |
} |
|
| 24 |
} |
|
| 25 | ||
| 26 | 30x |
result |
| 27 |
} |
|
| 28 | ||
| 29 |
get_nested_value <- function(config, key) {
|
|
| 30 | 751x |
parts <- strsplit(key, ".", fixed = TRUE)[[1]] |
| 31 | ||
| 32 | 751x |
for (p in parts) {
|
| 33 | 1502x |
config <- config[[p]] |
| 34 |
} |
|
| 35 | 751x |
config |
| 36 |
} |
|
| 37 | ||
| 38 |
set_nested_value <- function(config, key, value) {
|
|
| 39 | 18x |
parts <- strsplit(key, ".", fixed = TRUE)[[1]] |
| 40 | ||
| 41 | 18x |
if (length(parts) == 1) {
|
| 42 | 9x |
config[[parts[1]]] <- value |
| 43 |
} else {
|
|
| 44 | 9x |
sub_key <- paste(parts[-1], collapse = ".") |
| 45 | 9x |
config[[parts[1]]] <- set_nested_value(config[[parts[1]]], sub_key, value) |
| 46 |
} |
|
| 47 | 18x |
config |
| 48 |
} |
|
| 49 | ||
| 50 |
check_magick_available <- function() {
|
|
| 51 | 27x |
requireNamespace("magick", quietly = TRUE)
|
| 52 |
} |
|
| 53 | ||
| 54 |
check_pdftools_available <- function() {
|
|
| 55 | 30x |
requireNamespace("pdftools", quietly = TRUE)
|
| 56 |
} |
|
| 57 | ||
| 58 |
#' Config.R |
|
| 59 |
#' |
|
| 60 |
#' Class for manging the library configuration options. Creates the default |
|
| 61 |
#' configuration without any source file, populates partial or missing config |
|
| 62 |
#' elements, stores the config file to local machine, and provides easy access |
|
| 63 |
#' methods for setting and getting config values. |
|
| 64 |
#' |
|
| 65 |
#' @examples |
|
| 66 |
#' |
|
| 67 |
#' # Creates the configuration instance. Checks automatically if there is |
|
| 68 |
#' # a previously stored configuration json file available for usage. Note |
|
| 69 |
#' # that you don't need to explicitly define the config file path for the |
|
| 70 |
#' # Config instance - by default the config file will be searched from and |
|
| 71 |
#' # written in user-specific configuration directory for the package. |
|
| 72 |
#' |
|
| 73 |
#' path <- tempfile(fileext = ".json") |
|
| 74 |
#' config <- Config$new(config_path = path) |
|
| 75 |
#' |
|
| 76 |
#' # Getting and setting configuration values |
|
| 77 |
#' |
|
| 78 |
#' value <- config$get("defailts.mode")
|
|
| 79 |
#' config$set("details.mode", "full")
|
|
| 80 |
#' |
|
| 81 |
#' # Saving the current configuration to local machine (to tmp folder with |
|
| 82 |
#' # the given explicit file path in initialization). |
|
| 83 |
#' |
|
| 84 |
#' config$save() |
|
| 85 |
#' |
|
| 86 |
#' @import jsonlite |
|
| 87 |
#' @import rappdirs |
|
| 88 |
#' @importFrom R6 R6Class |
|
| 89 |
#' |
|
| 90 |
#' @field schema configuration schema |
|
| 91 |
#' @field config current configuration data |
|
| 92 |
#' @field path configuration json file path |
|
| 93 |
#' |
|
| 94 |
#' @export |
|
| 95 |
#' |
|
| 96 |
Config <- R6::R6Class( |
|
| 97 |
"Config", |
|
| 98 |
public = list( |
|
| 99 |
schema = NULL, |
|
| 100 |
config = NULL, |
|
| 101 |
path = NULL, |
|
| 102 | ||
| 103 |
#' @description |
|
| 104 |
#' Constructor for initializing the configuration. Checks the local machine |
|
| 105 |
#' for existing configuration file is load_config = TRUE. Ensures that all |
|
| 106 |
#' the project configuration values are included. |
|
| 107 |
#' |
|
| 108 |
#' @param load_config load configuration from local machine if available |
|
| 109 |
#' @param config_path location of the used/stored configuration json file |
|
| 110 |
#' |
|
| 111 |
initialize = function(load_config = TRUE, config_path = NULL) {
|
|
| 112 | 28x |
self$schema <- self$get_default_schema() |
| 113 | 28x |
self$config <- self$get_default_config() |
| 114 | ||
| 115 | 28x |
if (is.null(config_path)) {
|
| 116 | 24x |
config_dir <- rappdirs::user_config_dir("verifyr2")
|
| 117 | 24x |
self$path <- file.path(config_dir, "config.json") |
| 118 |
} else {
|
|
| 119 | 4x |
self$path <- config_path |
| 120 |
} |
|
| 121 | ||
| 122 | 28x |
if (load_config && file.exists(self$path)) {
|
| 123 | 3x |
file_config <- jsonlite::read_json(self$path, simplifyVector = TRUE) |
| 124 | 3x |
self$config <- merge_values(self$get_default_schema(), file_config) |
| 125 | 3x |
self$config <- self$get_default_config() |> merge_values(self$config) |
| 126 |
} |
|
| 127 |
}, |
|
| 128 | ||
| 129 |
#' @description |
|
| 130 |
#' Mehod for getting configuration value based on configuration key. |
|
| 131 |
#' Configuratio item children are separated with a dot in the key notation. |
|
| 132 |
#' |
|
| 133 |
#' @param key configuration property key for which to get the value |
|
| 134 |
#' |
|
| 135 |
get = function(key) {
|
|
| 136 | 751x |
get_nested_value(self$config, key) |
| 137 |
}, |
|
| 138 | ||
| 139 |
#' @description |
|
| 140 |
#' Mehod for setting configuration value based on configuration key. |
|
| 141 |
#' Configuration item children are separated with a dot in the key notation. |
|
| 142 |
#' |
|
| 143 |
#' @param key configuration property key for which to get the value |
|
| 144 |
#' @param value value to set for the specified configuration key |
|
| 145 |
#' |
|
| 146 |
set = function(key, value) {
|
|
| 147 | 9x |
self$config <- set_nested_value(self$config, key, value) |
| 148 |
}, |
|
| 149 | ||
| 150 |
#' @description |
|
| 151 |
#' Method for saving the current configuration data into local machine. |
|
| 152 |
#' |
|
| 153 |
save = function() {
|
|
| 154 | 1x |
dir.create(dirname(self$path), showWarnings = FALSE, recursive = TRUE) |
| 155 | 1x |
jsonlite::write_json(self$config, self$path, pretty = TRUE) |
| 156 |
}, |
|
| 157 | ||
| 158 |
#' @description |
|
| 159 |
#' Helper method for getting configuration default values. These default |
|
| 160 |
#' values will be used in the configuration in case the configuration |
|
| 161 |
#' properties are not present previously. |
|
| 162 |
#' |
|
| 163 |
get_default_config = function() {
|
|
| 164 | 31x |
defaults <- list() |
| 165 | ||
| 166 | 31x |
for (group in names(self$schema)) {
|
| 167 | 124x |
defaults[[group]] <- list() |
| 168 | ||
| 169 | 124x |
for (key in names(self$schema[[group]])) {
|
| 170 | 279x |
if (key != "description") {
|
| 171 | 155x |
defaults[[group]][[key]] <- self$schema[[group]][[key]]$default |
| 172 |
} |
|
| 173 |
} |
|
| 174 |
} |
|
| 175 | 31x |
defaults |
| 176 |
}, |
|
| 177 | ||
| 178 |
#' @description |
|
| 179 |
#' Method for getting the full configuration schema. Apart from the |
|
| 180 |
#' configuration data, the schema contains property descriptions as well as |
|
| 181 |
#' all possible values for the configuration properties. |
|
| 182 |
#' |
|
| 183 |
get_default_schema = function() {
|
|
| 184 | 31x |
schema <- list( |
| 185 | 31x |
generic = list( |
| 186 | 31x |
description = "Generic options", |
| 187 | 31x |
debug = list( |
| 188 | 31x |
description = "Debugging enabled", |
| 189 | 31x |
options = c("yes", "no"),
|
| 190 | 31x |
default = "no" |
| 191 |
), |
|
| 192 | 31x |
images = list( |
| 193 | 31x |
description = "Process embedded images", |
| 194 | 31x |
options = c("yes", "no"),
|
| 195 | 31x |
default = "yes" |
| 196 |
) |
|
| 197 |
), |
|
| 198 | 31x |
rtf = list( |
| 199 | 31x |
description = "RTF comparison (summary and details)", |
| 200 | 31x |
images = list( |
| 201 | 31x |
description = "Process embedded images", |
| 202 | 31x |
options = c("yes", "no"),
|
| 203 | 31x |
default = "yes" |
| 204 |
) |
|
| 205 |
), |
|
| 206 | 31x |
pdf = list( |
| 207 | 31x |
description = "PDF comparison (summary)", |
| 208 | 31x |
details = list( |
| 209 | 31x |
description = "Process PDF detailed comparison", |
| 210 | 31x |
options = c("yes", "no"),
|
| 211 | 31x |
default = "yes" |
| 212 |
) |
|
| 213 |
), |
|
| 214 | 31x |
details = list( |
| 215 | 31x |
description = "Details comparison", |
| 216 | 31x |
mode = list( |
| 217 | 31x |
description = "Mode", |
| 218 | 31x |
options = c("full", "summary"),
|
| 219 | 31x |
default = "summary" |
| 220 |
) |
|
| 221 |
) |
|
| 222 |
) |
|
| 223 | ||
| 224 | 31x |
if (!check_magick_available()) {
|
| 225 | 4x |
schema[["generic"]][["images"]] <- list( |
| 226 | 4x |
description = "Process embedded images (missing magick library)", |
| 227 | 4x |
options = c("no"),
|
| 228 | 4x |
default = "no" |
| 229 |
) |
|
| 230 | ||
| 231 | 4x |
schema[["rtf"]][["images"]] <- list( |
| 232 | 4x |
description = "Process embedded images (missing magick library)", |
| 233 | 4x |
options = c("no"),
|
| 234 | 4x |
default = "no" |
| 235 |
) |
|
| 236 |
} |
|
| 237 | ||
| 238 | 31x |
if (!check_pdftools_available()) {
|
| 239 | 1x |
schema[["pdf"]][["details"]] <- list( |
| 240 | 1x |
description = "Process PDF details (missing pdftools library)", |
| 241 | 1x |
options = c("no"),
|
| 242 | 1x |
default = "no" |
| 243 |
) |
|
| 244 |
} |
|
| 245 | ||
| 246 | 31x |
schema |
| 247 |
} |
|
| 248 |
) |
|
| 249 |
) |
| 1 |
#' One row list for two distinct files |
|
| 2 |
#' |
|
| 3 |
#' \code{list_files} List single file row based on the explicit parameter
|
|
| 4 |
#' files. This is a conveniency function for building same structure list for |
|
| 5 |
#' direct two file comparison case. |
|
| 6 |
#' |
|
| 7 |
#' @param file1 character, giving the the full file path of the first file |
|
| 8 |
#' @param file2 character, giving the the full file path of the second file |
|
| 9 |
#' |
|
| 10 |
#' @return Returns a tibble, \code{selected_files} with 2 columns \code{file1},
|
|
| 11 |
#' \code{file2}
|
|
| 12 |
#' |
|
| 13 |
#' @examples |
|
| 14 |
#' |
|
| 15 |
#' path1 <- "/extdata/base_files/file2_additional_rows.rtf" |
|
| 16 |
#' file1 <- paste0(fs::path_package(path1, package = "verifyr2")) |
|
| 17 |
#' |
|
| 18 |
#' path2 <- "/extdata/compare_files/file3_changed_rows.rtf" |
|
| 19 |
#' file2 <- paste0(fs::path_package(path2, package = "verifyr2")) |
|
| 20 |
#' |
|
| 21 |
#' verifyr2::list_files(file1, file2) |
|
| 22 |
#' |
|
| 23 |
#' @export |
|
| 24 | ||
| 25 |
list_files <- function(file1, file2) {
|
|
| 26 | ||
| 27 |
## do the comparison only if both of the files exist |
|
| 28 | 3x |
if (file.exists(file1) && file.exists(file2)) {
|
| 29 | ||
| 30 | 1x |
data <- tibble::tibble( |
| 31 | 1x |
"file1" = file1, |
| 32 | 1x |
"file2" = file2 |
| 33 |
) |
|
| 34 | ||
| 35 | 1x |
return(data) |
| 36 |
} |
|
| 37 | ||
| 38 | 2x |
NULL |
| 39 |
} |