1# Copyright (C) 2021 Brodie Gaslam 2# 3# This file is part of "diffobj - Diffs for R Objects" 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 2 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# Go to <https://www.r-project.org/Licenses/GPL-2> for a copy of the license. 16 17#' @include html.R 18#' @include finalizer.R 19#' @include pager.R 20 21NULL 22 23# maybe this shouldn't be an S4 class since the function slot doesn't work 24# for classed functions (e.g. the ones produced by crayon) 25 26#' Functions Used for Styling Diff Components 27#' 28#' Except for \code{container} every function specified here should be 29#' vectorized and apply formatting to each element in a character vectors. The 30#' functions must accept at least one argument and require no more than one 31#' argument. The text to be formatted will be passed as a character vector 32#' as the first argument to each function. 33#' 34#' These functions are applied in post processing steps. The \code{diff*} 35#' methods do not do any of the formatting. Instead, the formatting is done 36#' only if the user requests to \code{show} the object. Internally, \code{show} 37#' first converts the object to a character vector using \code{as.character}, 38#' which applies every formatting function defined here except for 39#' \code{container}. Then \code{show} applies \code{container} before 40#' forwarding the result to the screen or pager. 41#' 42#' @note the slots are set to class \dQuote{ANY} to allow classed functions 43#' such as those defined in the \code{crayon} package. Despite this seemingly 44#' permissive slot definition, only functions are allowed in the slots by 45#' the validation functions. 46#' @param container function used primarily by HTML styles to generate an 47#' outermost \code{DIV} that allows for CSS targeting of its contents 48#' (see \code{\link{cont_f}} for a function generator appropriate for use 49#' here) 50#' @param line function 51#' @param line.insert function 52#' @param line.delete function 53#' @param line.match function 54#' @param line.guide function formats guide lines (see \code{\link{guides}}) 55#' @param text function 56#' @param text.insert function 57#' @param text.delete function 58#' @param text.match function 59#' @param text.guide function formats guide lines (see \code{\link{guides}}) 60#' @param gutter function 61#' @param gutter.insert function 62#' @param gutter.delete function 63#' @param gutter.match function 64#' @param gutter.guide function 65#' @param gutter.pad function 66#' @param header function to format each hunk header with 67#' @param banner function to format entire banner 68#' @param banner.insert function to format insertion banner 69#' @param banner.delete function to format deletion banner 70#' @param meta function format meta information lines 71#' @param context.sep function to format the separator used to visually 72#' distinguish the A and B hunks in \dQuote{context} \code{mode} 73#' @return a StyleFuns S4 object 74#' @seealso \code{\link{Style}} 75#' @rdname StyleFuns 76#' @export StyleFuns 77#' @exportClass StyleFuns 78 79StyleFuns <- setClass( 80 "StyleFuns", 81 slots=c( 82 container="ANY", row="ANY", 83 line="ANY", line.insert="ANY", line.delete="ANY", line.match="ANY", 84 line.guide="ANY", line.fill="ANY", 85 text="ANY", text.insert="ANY", text.delete="ANY", text.match="ANY", 86 text.guide="ANY", text.fill="ANY", 87 banner="ANY", banner.insert="ANY", banner.delete="ANY", 88 gutter="ANY", 89 gutter.insert="ANY", gutter.insert.ctd="ANY", 90 gutter.delete="ANY", gutter.delete.ctd="ANY", 91 gutter.match="ANY", gutter.match.ctd="ANY", 92 gutter.guide="ANY", gutter.guide.ctd="ANY", 93 gutter.fill="ANY", gutter.fill.ctd="ANY", 94 gutter.context.sep="ANY", gutter.context.sep.ctd="ANY", 95 gutter.pad="ANY", 96 word.insert="ANY", word.delete="ANY", 97 context.sep="ANY", header="ANY", meta="ANY", trim="ANY" 98 ), 99 prototype=list( 100 container=identity, row=identity, 101 banner=identity, banner.insert=identity, banner.delete=identity, 102 line=identity, line.insert=identity, line.delete=identity, 103 line.match=identity, line.guide=identity, line.fill=identity, 104 text=identity, text.insert=identity, text.delete=identity, 105 text.match=identity, text.guide=identity, text.fill=identity, 106 gutter=identity, gutter.pad=identity, 107 gutter.insert=identity, gutter.insert.ctd=identity, 108 gutter.delete=identity, gutter.delete.ctd=identity, 109 gutter.match=identity, gutter.match.ctd=identity, 110 gutter.guide=identity, gutter.guide.ctd=identity, 111 gutter.fill=identity, gutter.fill.ctd=identity, 112 gutter.context.sep=identity, gutter.context.sep.ctd=identity, 113 word.insert=identity, word.delete=identity, 114 header=identity, 115 context.sep=identity, 116 meta=identity, 117 trim=identity 118 ), 119 validity=function(object){ 120 for(i in slotNames(object)) { 121 if(!is.function(slot(object, i))) 122 return(paste0("Argument `", i, "` should be a function.")) 123 if(has_non_def_formals(tail(formals(slot(object, i)), -1L))) 124 return( 125 paste0( 126 "Argument `", i, 127 "` may not have non-default formals argument after the first." 128 ) ) 129 } 130 TRUE 131 } 132) 133StyleFunsAnsi <- setClass( 134 "StyleFunsAnsi", contains="StyleFuns", 135 prototype=list( 136 word.insert=crayon::green, word.delete=crayon::red, 137 gutter.insert=crayon::green, gutter.insert.ctd=crayon::green, 138 gutter.delete=crayon::red, gutter.delete.ctd=crayon::red, 139 gutter.guide=crayon::silver, gutter.guide.ctd=crayon::silver, 140 gutter.fill=crayon::silver, gutter.fill.ctd=crayon::silver, 141 gutter.context.sep=crayon::silver, gutter.context.sep.ctd=crayon::silver, 142 header=crayon::cyan, 143 meta=crayon::silver, 144 line.guide=crayon::silver, 145 context.sep=crayon::silver, 146 trim=crayon::silver 147 ) 148) 149#' Character Tokens Used in Diffs 150#' 151#' Various character tokens are used throughout diffs to provide visual cues. 152#' For example, gutters will contain characters that denote deletions and 153#' insertions (\code{<} and \code{>} by default). 154#' 155#' @param gutter.insert character(1L) text to use as visual cue to indicate 156#' whether a diff line is an insertion, defaults to \dQuote{> } 157#' @param gutter.insert.ctd character(1L) if a diff line is wrapped, the 158#' visual cue shifts to this character to indicate wrapping occured 159#' @param gutter.delete character(1L) see \code{gutter.insert} above 160#' @param gutter.delete.ctd character(1L) see \code{gutter.insert.ctd} above 161#' @param gutter.match character(1L) see \code{gutter.insert} above 162#' @param gutter.match.ctd character(1L) see \code{gutter.insert.ctd} above 163#' @param gutter.guide character(1L) see \code{gutter.insert} above 164#' @param gutter.guide.ctd character(1L) see \code{gutter.insert.ctd} above 165#' @param gutter.fill character(1L) see \code{gutter.insert} above 166#' @param gutter.fill.ctd character(1L) see \code{gutter.insert.ctd} above 167#' @param gutter.pad character(1L) separator between gutter characters and the 168#' rest of a line in a diff 169#' @param pad.col character(1L) separator between columns in side by side mode 170#' @return a StyleText S4 object 171#' @seealso \code{\link{Style}} 172#' @rdname StyleText 173#' @export StyleText 174#' @exportClass StyleText 175 176StyleText <- setClass( 177 "StyleText", 178 slots=c( 179 gutter.insert="character", gutter.insert.ctd="character", 180 gutter.delete="character", gutter.delete.ctd="character", 181 gutter.match="character", gutter.match.ctd="character", 182 gutter.guide="character", gutter.guide.ctd="character", 183 gutter.fill="character", gutter.fill.ctd="character", 184 gutter.context.sep="character", gutter.context.sep.ctd="character", 185 gutter.pad="character", 186 context.sep="character", 187 pad.col="character", 188 line.break="character" 189 ), 190 prototype=list( 191 gutter.insert=">", gutter.insert.ctd=":", 192 gutter.delete="<", gutter.delete.ctd=":", 193 gutter.match=" ", gutter.match.ctd=" ", 194 gutter.guide="~", gutter.guide.ctd="~", 195 gutter.fill="~", gutter.fill.ctd="~", 196 gutter.context.sep="~", gutter.context.sep.ctd="~", 197 gutter.pad=" ", context.sep="----------", 198 pad.col=" ", 199 line.break="\n" 200 ), 201 validity=function(object){ 202 for(i in slotNames(object)) if(!is.chr.1L(slot(object, i))) 203 return(paste0("Argument `", i, "` must be character(1L) and not NA.")) 204 TRUE 205 } 206) 207#' Styling Information for Summaries 208#' 209#' @export 210#' @rdname StyleSummary 211#' @slot container function applied to entire summary 212#' @slot body function applied to everything except the actual map portion of 213#' the summary 214#' @slot detail function applied to section showing how many deletions / 215#' insertions, etc. occurred 216#' @slot map function applied to the map portion of the summary 217 218StyleSummary <- setClass("StyleSummary", 219 slots=c(container="ANY", body="ANY", map="ANY", detail="ANY"), 220 prototype=list( 221 container=function(x) sprintf("\n%s\n", paste0(x, collapse="")), 222 body=identity, 223 detail=function(x) sprintf("\n%s\n", paste0(" ", x, collapse="")), 224 map=function(x) sprintf("\n%s", paste0(" ", x, collapse="\n")) 225 ), 226 validity=function(object) { 227 fun.slots <- c("container", "body", "map", "detail") 228 for(i in fun.slots) { 229 if(!isTRUE(is.one.arg.fun(slot(object, i)))) 230 return( 231 "Slot ", i, " must contain a function that accepts at least one ", 232 "argument and requires no more than one argument." 233 ) 234 } 235 TRUE 236 } 237) 238#' @rdname StyleSummary 239#' @export 240 241StyleSummaryHtml <- setClass("StyleSummaryHtml", contains="StyleSummary", 242 prototype=list( 243 container=function(x) div_f("diffobj-summary")(paste0(x, collapse="")), 244 body=div_f("body"), 245 detail=div_f("detail"), 246 map=div_f("map") 247) ) 248#' Customize Appearance of Diff 249#' 250#' S4 objects that expose the formatting controls for \code{Diff} 251#' objects. Many predefined formats are defined as classes that extend the 252#' base \code{Style} class. You may fine tune styles by either extending 253#' the pre-defined classes, or modifying an instance thereof. 254#' 255#' @section Pre-defined Classes: 256#' 257#' Pre-defined classes are used to populate the \code{\link{PaletteOfStyles}} 258#' object, which in turn allows the \code{diff*} methods to pick the 259#' appropriate \code{Style} for each combination of the \code{format}, 260#' \code{color.mode}, and \code{brightness} parameters when the \code{style} 261#' parameter is set to \dQuote{auto}. The following classes are pre-defined: 262#' 263#' \itemize{ 264#' \item \code{StyleRaw}: No styles applied 265#' \item \code{StyleAnsi8NeutralRgb} 266#' \item \code{StyleAnsi8NeutralYb} 267#' \item \code{StyleAnsi256LightRgb} 268#' \item \code{StyleAnsi256LightYb} 269#' \item \code{StyleAnsi256DarkRgb} 270#' \item \code{StyleAnsi256DarkYb} 271#' \item \code{StyleHtmlLightRgb} 272#' \item \code{StyleHtmlLightYb} 273#' } 274#' Each of these classes has an associated constructor function with the 275#' same name (see examples). Objects instantiated from these classes 276#' may also be used directly as the value for the \code{style} parameter to the 277#' \code{diff*} methods. This will override the automatic selection process 278#' that uses \code{\link{PaletteOfStyles}}. If you wish to tweak an 279#' auto-selected style rather than explicitly specify one, pass a parameter 280#' list instead of a \code{Style} objects as the \code{style} parameter to the 281#' \code{diff*} methods (see examples). 282#' 283#' There are predefined classes for most combinations of 284#' \code{format/color.mode/brightness}, but not all. For example, there are 285#' only \dQuote{light} \code{brightness} defined for the \dQuote{html} 286#' \code{format}, and those classes are re-used for all possible 287#' \code{brightness} values, and the 8 color ANSI neutral classes are used 288#' for the 256 color neutral selections as well. 289#' 290#' To get a preview of what a style looks like just instantiate 291#' an object; the \code{show} method will output a trivial diff to screen with 292#' styles applied. Note that for ANSI styles of the dark and light variety 293#' the show method colors the terminal background and foregrounds in compatible 294#' colors. In normal usage the terminal background and foreground colors are 295#' left untouched so you should not expect light styles to look good on dark 296#' background and vice versa even if they render correctly when showing the 297#' style object. 298#' 299#' @section Style Structure: 300#' 301#' Most of the customization is done by specifying functions that operate on 302#' character vectors and return a modified character vector of the same length. 303#' The intended use case is to pass \code{crayon} functions such as 304#' \code{crayon::red}, although you may pass any function of your liking 305#' that behaves as described. Formatting functions are expected to return their 306#' inputs formatted in such a way that their \emph{display} width is unchanged. 307#' If your formatting functions change display width output may not render 308#' properly, particularly when using \code{mode="sidebyside"}. 309#' 310#' The visual representation of the diff has many nested components. The 311#' functions you specify here will be applied starting with the innermost ones. 312#' A schematic of the various component that represent an inserted line follows 313#' (note \dQuote{insert} abbreviated to \dQuote{ins}, and \dQuote{gutter} 314#' abbreviated to \dQuote{gtr}): 315#' \preformatted{+- line ---------------------------------------------------+ 316#' |+- line.ins ---------------------------------------------+| 317#' ||+- gtr ------------------------++- text ---------------+|| 318#' |||+- gtr.ins ---++- gtr.pad ---+||+- text.ins ---------+||| 319#' |||| || |||| +- word.ins -+|||| 320#' |||| gtr.ins.txt || gtr.pad.txt |||| DIFF | TEXT HERE ||||| 321#' |||| || |||| +------------+|||| 322#' |||+-------------++-------------+||+--------------------+||| 323#' ||+------------------------------++----------------------+|| 324#' |+--------------------------------------------------------+| 325#' +----------------------------------------------------------+ 326#' } 327#' A similar model applies to deleted and matching lines. The boxes represent 328#' functions. \code{gutter.insert.txt} represents the text to use in the gutter 329#' and is not a function. \code{DIFF TEXT HERE} is text from the objects being 330#' diffed, with the portion that has different words inside the 331#' \code{word.insert}. \code{gutter.pad} and \code{gutter.pad.txt} are used to 332#' separate the gutter from the text and usually end up resolving to a space. 333#' 334#' Most of the functions defined here default to \code{\link{identity}}, but 335#' you are given the flexibility to fully format the diff. See 336#' \code{\link{StyleFuns}} and \code{\link{StyleText}} for a full listing of 337#' the adjustable elements. 338#' 339#' In side-by-side mode there are two \dQuote{lines} per screen line, each with 340#' the structure described here. 341#' 342#' The structure described here may change in the future. 343#' 344#' @section HTML Styles: 345#' 346#' If you use a \code{Style} that inherits from \code{StyleHtml} the 347#' diff will be wrapped in HTML tags, styled with CSS, and output to 348#' \code{getOption("viewer")} if your IDE supports it (e.g. Rstudio), or 349#' directly to the browser otherwise, assuming that the default 350#' \code{\link{Pager}} or a correctly configured pager that inherits from 351#' \code{\link{PagerBrowser}} is in effect. Otherwise, the raw HTML will be 352#' output to your terminal. 353#' 354#' By default HTML output sent to the viewer/browser is a full stand-alone 355#' webpage with CSS styles to format and color the diff, and JS code to 356#' handle scaling. The CSS and JS is read from the 357#' \link[=webfiles]{default files} and injected into the HTML to simplify 358#' packaging of the output. You can customize the CSS and JS by using the 359#' \code{css} and \code{js} arguments respectively, but read the rest of this 360#' documentation section if you plan on doing so. 361#' 362#' Should you want to capture the HTML output for use elsewhere, you can do 363#' so by using \code{as.character} on the return value of the \code{diff*} 364#' methods. If you want the raw HTML without any of the headers, CSS, and 365#' JS use \code{html.ouput="diff.only"} when you instantiate the 366#' \code{StyleHtml} object (see examples), or disable the \code{\link{Pager}}. 367#' Another option is \code{html.output="diff.w.style"} which will add 368#' \code{<style>} tags with the CSS, but without wrapping those in \code{<head>} 369#' tags. This last option results in illegal HTML with a \code{<style>} block 370#' outside of the \code{<head>} block, but appears to work and is useful if you 371#' want to embed HTML someplace but do not have access to the headers. 372#' 373#' If you wish to modify the CSS styles you should do so cautiously. The 374#' HTML and CSS work well together out of the box, but may not take to kindly 375#' to modifications. The safest changes you can make are to the colors of the 376#' scheme. You also probably should not modify the functions in the 377#' \code{@funs} slot of the \code{StyleHtml} object. If you want to provide 378#' your own custom styles make a copy of the file at the location returned by 379#' \code{diffobj_css()}, modify it to your liking, and pass the location of your 380#' modified sheet back via the \code{css} argument (see examples). 381#' 382#' The javascript controls the scaling of the output such that its width fits 383#' in the viewport. If you wish to turn of this behavior you can do so via the 384#' \code{scale} argument. You may need to modify the javascript if you modify 385#' the \code{@funs} functions, but otherwise you are probably best off leaving 386#' the javascript untouched. You can provide the location of a modified 387#' javascript file via the \code{js} argument. 388#' 389#' Both the CSS and JS files can be specified via options, 390#' \dQuote{diffobj.html.css}, and \dQuote{diffobj.html.js} respectively. 391#' 392#' If you define your own custom \code{StyleHtml} object you may want to modify 393#' the slot \code{@funs@container}. This slot contains a function that is 394#' applied to the entire diff output. For example, \code{StyleHtmlLightRgb} 395#' uses \code{@funs@container <- cont_f("light", "rgb")}. \code{cont_f} returns 396#' a function that accepts a character vector as an argument and returns 397#' that value wrapped in a \code{DIV} block with class 398#' \dQuote{"diffobj-container light rgb"}. This allows the CSS style sheet to 399#' target the \code{Diff} elements with the correct styles. 400#' 401#' @section Modifying Style Parameters Directly: 402#' 403#' Often you will want to specify some of the style parameters (e.g. 404#' \code{scale} for html styles) while still relying on the default style 405#' selection to pick the specific style. You can do so by passing a list to the 406#' \code{style} parameter of the \code{\link[=diffPrint]{diff*}} methods. 407#' See examples. 408#' 409#' @section New Classes: 410#' 411#' You can in theory create entirely new classes that extent \code{Style}. For 412#' example you could generate a class that renders the diff in \code{grid} 413#' graphics. Note however that we have not tested such extensions and it is 414#' possible there is some embedded code that will misbehave with such a new 415#' class. 416#' 417#' @rdname Style 418#' @export Style 419#' @exportClass Style 420#' @param funs a \code{\link{StyleFuns}} object that contains all the functions 421#' represented above 422#' @param text a \code{\link{StyleText}} object that contains the non-content 423#' text used by the diff (e.g. \code{gutter.insert.txt}) 424#' @param summary a \code{\link{StyleSummary}} object that contains formatting 425#' functions and other meta data for rendering summaries 426#' @param wrap TRUE or FALSE, whether the text should be hard wrapped to fit in 427#' the console 428#' @param pad TRUE or FALSE, whether text should be right padded 429#' @param pager what type of \code{\link{Pager}} to use 430#' @param nchar.fun function to use to count characters; intended mostly for 431#' internal use (used only for gutters as of version 0.2.0). 432#' @param wrap TRUE or FALSE, whether text should be hard wrapped at 433#' \code{disp.width} 434#' @param na.sub what character value to substitute for NA elements; NA elements 435#' are generated when lining up side by side diffs by adding padding rows; by 436#' default the text styles replace these with a blank character string, and 437#' the HTML styles leave them as NA for the HTML formatting functions to deal 438#' with 439#' @param blank sub what character value to replace blanks with; needed in 440#' particular for HTML rendering (uses \code{" "}) to prevent lines from 441#' collapsing 442#' @param disp.width how many columns the text representation of the objects to 443#' diff is allowed to take up before it is hard wrapped (assuming \code{wrap} 444#' is TRUE). See param \code{disp.width} for \code{\link{diffPrint}}. 445#' @param finalizer function that accepts at least two parameters and requires 446#' no more than two parameters, will receive as the first parameter the 447#' the object to render (either a \code{Diff} or a \code{DiffSummary} 448#' object), and the text representation of that object as the second 449#' argument. This allows final modifications to the character output so that 450#' it is displayed correctly by the pager. For example, \code{StyleHtml} 451#' objects use it to generate HTML headers if the \code{Diff} is destined to 452#' be displayed in a browser. The object themselves are passed along to 453#' provide information about the paging device and other contextual data to 454#' the function. 455#' @param html.output (\code{StyleHtml} objects only) one of: 456#' \itemize{ 457#' \item \dQuote{page}: Include all HTML/CSS/JS required to create a 458#' stand-alone web page with the diff; in this mode the diff string will 459#' be re-encoded with \code{\link{enc2utf8}} and the HTML page encoding 460#' will be declared as UTF-8. 461#' \item \dQuote{diff.w.style}: The CSS and HTML, but without any of the 462#' outer tags that would make it a proper HTML page (i.e. no 463#' \code{<html>/<head>} tags or the like) and without the JS; note that 464#' technically this is illegal HTML since we have \code{<style>} tags 465#' floating outside of \code{<head>} tags, but it seems to work in most 466#' browsers. 467#' \item \dQuote{diff.only}: Like \dQuote{diff.w.style}, but without the CSS 468#' \item \dQuote{auto}: Pick one of the above based on \code{Pager}, will 469#' chose \dQuote{page} if the pager is of type \code{PagerBrowser} (as in 470#' that case the output is destined to be displayed in a browser like 471#' device), or \dQuote{diff.only} if it is not. 472#' } 473#' @param escape.html.entities (\code{StyleHtml} objects only) TRUE (default) 474#' or FALSE, whether to escape HTML entities in the input 475#' @param scale (\code{StyleHtml} objects only) TRUE (default) or FALSE, 476#' whether to scale HTML output to fit to the viewport 477#' @param css (\code{StyleHtml} objects only) path to file containing CSS styles 478#' to style HTML output with 479#' @param js (\code{StyleHtml} objects only) path to file containing Javascript 480#' used for scaling output to viewports. 481#' @return Style S4 object 482#' @examples 483#' \dontrun{ 484#' ## Create a new style based on existing style by changing 485#' ## gutter symbols and guide color; see `?StyleFuns` and 486#' ## `?StyleText` for a full list of adjustable elements 487#' my.style <- StyleAnsi8NeutralYb() 488#' my.style ## `show` method gives you a preview of the style 489#' my.style@text@gutter.insert <- "+++" 490#' my.style@text@gutter.delete <- "---" 491#' my.style@funs@text.guide <- crayon::green 492#' my.style ## Notice gutters and guide color 493#' 494#' ## Provide a custom style sheet; here we assume there is a style sheet at 495#' ## `HOME/web/mycss.css` 496#' my.css <- file.path(path.expand("~"), "web", "mycss.css") 497#' diffPrint(1:5, 2:6, style=StyleHtmlLightYb(css=my.css)) 498#' 499#' ## Turn of scaling; notice how we pass a list to `style` 500#' ## and we do not need to specify a specific style 501#' diffPrint(letters, letters[-5], format="html", style=list(scale=FALSE)) 502#' 503#' ## Alternatively we can do the same by specifying a style, but we must 504#' ## give an exact html style instead of relying on preferences to pick 505#' ## one for us 506#' my.style <- StyleHtmlLightYb(scale=FALSE) 507#' diffPrint(letters, letters[-5], style=my.style) 508#' } 509#' ## Return only the raw HTML without any of the headers 510#' as.character( 511#' diffPrint(1:5, 2:6, format="html", style=list(html.output="diff.only")) 512#' ) 513 514Style <- setClass("Style", contains="VIRTUAL", 515 slots=c( 516 funs="StyleFuns", 517 text="StyleText", 518 summary="StyleSummary", 519 nchar.fun="ANY", 520 wrap="logical", 521 pad="logical", 522 finalizer="function", 523 pager="Pager", 524 na.sub="character", 525 blank.sub="character", 526 disp.width="integer" 527 ), 528 prototype=list( 529 funs=StyleFuns(), 530 text=StyleText(), 531 wrap=TRUE, 532 pad=TRUE, 533 pager=PagerOff(), 534 finalizer=function(x, y) y, 535 na.sub="", 536 blank.sub="", 537 disp.width=0L, 538 nchar.fun=nchar2 # even raw input can have SGR in it 539 ), 540 validity=function(object){ 541 # ## no longer true with nchar2 and support sgr parameter 542 # if(!isTRUE(is.one.arg.fun(object@nchar.fun))) { 543 # return(paste0( 544 # "Slot `nchar.fun` should be a function with at least one argument that ", 545 # "doesn't require more than one argument" 546 # ) ) 547 # } 548 if(!is.TF(object@wrap)) 549 return("Slot `wrap` must be TRUE or FALSE") 550 if(!is.TF(object@pad)) 551 return("Slot `pad` must be TRUE or FALSE") 552 if(length(object@na.sub) != 1L) 553 return("Slot `na.sub` must be character(1L)") 554 if(length(object@blank.sub) != 1L) 555 return("Slot `na.sub` must be character(1L)") 556 if(!is.int.1L(object@disp.width) || object@disp.width < 0L) 557 return("Slot `disp.width` must be integer(1L), positive, and not NA") 558 fin.args <- formals(object@finalizer) 559 if(length(fin.args) < 2L) 560 return( 561 "Slot `finalizer` must be a function with at least two parameters." 562 ) 563 if(length(fin.args) > 2L && has_non_def_formals(tail(fin.args, -2L))) 564 return( 565 paste0( 566 "Slot `finalizer` must be a function with no non-default parameters ", 567 "other than the first two." 568 ) ) 569 } 570) 571setClass("Light", contains="VIRTUAL") 572setClass("Dark", contains="VIRTUAL") 573setClass("Neutral", contains="VIRTUAL") 574 575setClass("Raw", contains="VIRTUAL") 576setClass("Ansi", contains="VIRTUAL") 577setClass("Html", contains="VIRTUAL") 578 579setClass("Rgb", contains="VIRTUAL") 580setClass("Yb", contains="VIRTUAL") 581 582#' @export StyleRaw 583#' @exportClass StyleRaw 584#' @rdname Style 585 586StyleRaw <- setClass( 587 "StyleRaw", contains=c("Style", "Raw") 588) 589setMethod( 590 "initialize", "StyleRaw", 591 function(.Object, ...) { 592 .Object@pager <- if(pager_is_less()) 593 PagerSystemLess() else PagerSystem() 594 callNextMethod(.Object, ...) 595}) 596 597#' @export StyleAnsi 598#' @exportClass StyleAnsi 599#' @rdname Style 600 601StyleAnsi <- setClass( 602 "StyleAnsi", contains=c("StyleRaw", "Ansi"), 603 prototype=list( 604 funs=StyleFunsAnsi(), 605 nchar.fun=nchar2 606 ) 607) 608#' @export StyleAnsi8NeutralRgb 609#' @exportClass StyleAnsi8NeutralRgb 610#' @rdname Style 611 612StyleAnsi8NeutralRgb <- setClass( 613 "StyleAnsi8NeutralRgb", contains=c("StyleAnsi", "Neutral", "Rgb") 614) 615#' @export StyleAnsi8NeutralYb 616#' @exportClass StyleAnsi8NeutralYb 617#' @rdname Style 618 619StyleAnsi8NeutralYb <- setClass( 620 "StyleAnsi8NeutralYb", contains=c("StyleAnsi", "Neutral", "Yb"), 621 prototype=list( 622 funs=StyleFunsAnsi( 623 word.insert=crayon::blue, word.delete=crayon::yellow, 624 gutter.insert=crayon::blue, 625 gutter.insert.ctd=crayon::blue, 626 gutter.delete=crayon::yellow, 627 gutter.delete.ctd=crayon::yellow 628 ) ) 629) 630#' @export StyleAnsi256LightRgb 631#' @exportClass StyleAnsi256LightRgb 632#' @rdname Style 633 634StyleAnsi256LightRgb <- setClass( 635 "StyleAnsi256LightRgb", contains=c("StyleAnsi", "Light", "Rgb"), 636 prototype=list( 637 funs=StyleFunsAnsi( 638 text.insert=crayon::make_style( 639 rgb(4, 5, 4, maxColorValue=5), bg=TRUE, colors=256 640 ), 641 text.delete=crayon::make_style( 642 rgb(5, 4, 4, maxColorValue=5), bg=TRUE, colors=256 643 ), 644 text.fill=crayon::make_style( 645 rgb(20, 20, 20, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256 646 ), 647 word.insert=crayon::make_style( 648 rgb(2, 4, 2, maxColorValue=5), bg=TRUE, colors=256 649 ), 650 word.delete=crayon::make_style( 651 rgb(4, 2, 2, maxColorValue=5), bg=TRUE, colors=256 652 ), 653 gutter.insert=crayon::make_style( 654 rgb(0, 3, 0, maxColorValue=5), colors=256 655 ), 656 gutter.insert.ctd=crayon::make_style( 657 rgb(0, 3, 0, maxColorValue=5), colors=256 658 ), 659 gutter.delete=crayon::make_style( 660 rgb(3, 0, 0, maxColorValue=5), colors=256 661 ), 662 gutter.delete.ctd=crayon::make_style( 663 rgb(3, 0, 0, maxColorValue=5), colors=256 664 ), 665 header=crayon::make_style( 666 rgb(0, 3, 3, maxColorValue=5), colors=256 667 ) 668) ) ) 669 670#' @export StyleAnsi256LightYb 671#' @exportClass StyleAnsi256LightYb 672#' @rdname Style 673 674StyleAnsi256LightYb <- setClass( 675 "StyleAnsi256LightYb", contains=c("StyleAnsi", "Light", "Yb"), 676 prototype=list( 677 funs=StyleFunsAnsi( 678 text.insert=crayon::make_style( 679 rgb(3, 3, 5, maxColorValue=5), bg=TRUE, colors=256 680 ), 681 text.delete=crayon::make_style( 682 rgb(4, 4, 2, maxColorValue=5), bg=TRUE, colors=256 683 ), 684 text.fill=crayon::make_style( 685 rgb(20, 20, 20, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256 686 ), 687 word.insert=crayon::make_style( 688 rgb(2, 2, 4, maxColorValue=5), bg=TRUE, colors=256 689 ), 690 word.delete=crayon::make_style( 691 rgb(3, 3, 1, maxColorValue=5), bg=TRUE, colors=256 692 ), 693 gutter.insert=crayon::make_style( 694 rgb(0, 0, 3, maxColorValue=5), colors=256 695 ), 696 gutter.insert.ctd=crayon::make_style( 697 rgb(0, 0, 3, maxColorValue=5), colors=256 698 ), 699 gutter.delete=crayon::make_style( 700 rgb(2, 1, 0, maxColorValue=5), colors=256 701 ), 702 gutter.delete.ctd=crayon::make_style( 703 rgb(2, 1, 0, maxColorValue=5), colors=256 704 ), 705 header=crayon::make_style( 706 rgb(0, 3, 3, maxColorValue=5), colors=256 707 ) 708) ) ) 709#' @export StyleAnsi256DarkRgb 710#' @exportClass StyleAnsi256DarkRgb 711#' @rdname Style 712 713StyleAnsi256DarkRgb <- setClass( 714 "StyleAnsi256DarkRgb", contains=c("StyleAnsi", "Dark", "Rgb"), 715 prototype=list( 716 funs=StyleFunsAnsi( 717 text.insert=crayon::make_style( 718 rgb(0, 1, 0, maxColorValue=5), bg=TRUE, colors=256 719 ), 720 text.delete=crayon::make_style( 721 rgb(1, 0, 0, maxColorValue=5), bg=TRUE, colors=256 722 ), 723 word.insert=crayon::make_style( 724 rgb(0, 3, 0, maxColorValue=5), bg=TRUE, colors=256 725 ), 726 word.delete=crayon::make_style( 727 rgb(3, 0, 0, maxColorValue=5), bg=TRUE, colors=256 728 ), 729 gutter.insert=crayon::make_style( 730 rgb(0, 2, 0, maxColorValue=5), colors=256 731 ), 732 gutter.insert.ctd=crayon::make_style( 733 rgb(0, 2, 0, maxColorValue=5), colors=256 734 ), 735 gutter.delete=crayon::make_style( 736 rgb(2, 0, 0, maxColorValue=5), colors=256 737 ), 738 gutter.delete.ctd=crayon::make_style( 739 rgb(2, 0, 0, maxColorValue=5), colors=256 740 ), 741 gutter.guide=crayon::make_style( 742 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 743 ), 744 gutter.guide.ctd=crayon::make_style( 745 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 746 ), 747 line.guide=crayon::make_style( 748 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 749 ), 750 gutter.fill=crayon::make_style( 751 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 752 ), 753 gutter.fill.ctd=crayon::make_style( 754 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 755 ), 756 text.fill=crayon::make_style( 757 rgb(2, 2, 2, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256 758 ), 759 gutter.context.sep=crayon::make_style( 760 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 761 ), 762 gutter.context.sep.ctd=crayon::make_style( 763 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 764 ), 765 context.sep=crayon::make_style( 766 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 767 ), 768 meta=crayon::make_style( 769 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 770 ), 771 trim=crayon::make_style( 772 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 773 ) 774) ) ) 775#' @export StyleAnsi256DarkYb 776#' @exportClass StyleAnsi256DarkYb 777#' @rdname Style 778 779StyleAnsi256DarkYb <- setClass( 780 "StyleAnsi256DarkYb", contains=c("StyleAnsi", "Dark", "Yb"), 781 prototype=list( 782 funs=StyleFunsAnsi( 783 text.insert=crayon::make_style( 784 rgb(0, 0, 2, maxColorValue=5), bg=TRUE, colors=256 785 ), 786 text.delete=crayon::make_style( 787 rgb(1, 1, 0, maxColorValue=5), bg=TRUE, colors=256 788 ), 789 word.insert=crayon::make_style( 790 rgb(0, 0, 5, maxColorValue=5), bg=TRUE, colors=256 791 ), 792 word.delete=crayon::make_style( 793 rgb(3, 2, 0, maxColorValue=5), bg=TRUE, colors=256 794 ), 795 gutter.insert=crayon::make_style( 796 rgb(0, 0, 3, maxColorValue=5), colors=256 797 ), 798 gutter.insert.ctd=crayon::make_style( 799 rgb(0, 0, 3, maxColorValue=5), colors=256 800 ), 801 gutter.delete=crayon::make_style( 802 rgb(1, 1, 0, maxColorValue=5), colors=256 803 ), 804 gutter.delete.ctd=crayon::make_style( 805 rgb(1, 1, 0, maxColorValue=5), colors=256 806 ), 807 header=crayon::make_style( 808 rgb(0, 3, 3, maxColorValue=5), colors=256 809 ), 810 gutter.guide=crayon::make_style( 811 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 812 ), 813 gutter.guide.ctd=crayon::make_style( 814 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 815 ), 816 line.guide=crayon::make_style( 817 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 818 ), 819 gutter.fill=crayon::make_style( 820 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 821 ), 822 gutter.fill.ctd=crayon::make_style( 823 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 824 ), 825 text.fill=crayon::make_style( 826 rgb(2, 2, 2, maxColorValue=23), bg=TRUE, grey=TRUE, colors=256 827 ), 828 gutter.context.sep=crayon::make_style( 829 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 830 ), 831 gutter.context.sep.ctd=crayon::make_style( 832 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 833 ), 834 context.sep=crayon::make_style( 835 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 836 ), 837 meta=crayon::make_style( 838 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 839 ), 840 trim=crayon::make_style( 841 rgb(13, 13, 13, maxColorValue=23), grey=TRUE, colors=256 842 ) 843) ) ) 844#' Return Location of Default HTML Support Files 845#' 846#' File location for default CSS and JS files. Note that these files are read 847#' and injected into the output HTML rather than referenced to simplify serving. 848#' 849#' @aliases diffobj_js 850#' @name webfiles 851#' @rdname webfiles 852#' @return path to the default CSS or JS file 853#' @examples 854#' diffobj_css() 855#' diffobj_js() 856 857NULL 858 859#' @export 860#' @rdname webfiles 861 862diffobj_css <- function() 863 file.path(system.file(package="diffobj"), "css", "diffobj.css") 864 865#' @export 866#' @rdname webfiles 867 868diffobj_js <- function() 869 file.path(system.file(package="diffobj"), "script", "diffobj.js") 870 871#' @export StyleHtml 872#' @exportClass StyleHtml 873#' @rdname Style 874 875StyleHtml <- setClass( 876 "StyleHtml", contains=c("Style", "Html"), 877 slots=c( 878 css="character", html.output="character", escape.html.entities="logical", 879 js="character", scale="logical" 880 ), 881 prototype=list( 882 funs=StyleFuns( 883 container=cont_f(), 884 row=div_f("diffobj-row"), 885 banner.insert=div_f("insert"), 886 banner.delete=div_f("delete"), 887 banner=div_f("diffobj-line banner"), 888 line.insert=div_f("insert"), 889 line.delete=div_f("delete"), 890 line.match=div_f("diffobj-match"), 891 line.guide=div_f("diffobj-guide"), 892 line.fill=div_f("diffobj-fill"), 893 line=div_f("diffobj-line"), 894 text.insert=div_f("insert"), 895 text.delete=div_f("delete"), 896 text.match=div_f("diffobj-match"), 897 text.guide=div_f("diffobj-guide"), 898 text.fill=div_f("diffobj-fill"), 899 text=div_f("diffobj-text"), 900 gutter.insert=div_f("insert"), 901 gutter.delete=div_f("delete"), 902 gutter.match=div_f("diffobj-match"), 903 gutter.guide=div_f("diffobj-guide"), 904 gutter.fill=div_f("diffobj-fill"), 905 gutter.pad=div_f("pad"), 906 gutter.context.sep=div_f(c("context_sep", "ctd")), 907 gutter.insert.ctd=div_f(c("insert", "ctd")), 908 gutter.delete.ctd=div_f(c("delete", "ctd")), 909 gutter.match.ctd=div_f(c("diffobj-match", "ctd")), 910 gutter.guide.ctd=div_f(c("diffobj-guide", "ctd")), 911 gutter.fill.ctd=div_f(c("diffobj-fill", "ctd")), 912 gutter.context.sep.ctd=div_f(c("context_sep", "ctd")), 913 gutter=div_f("diffobj-gutter"), 914 context.sep=div_f("context_sep"), 915 word.insert=span_f(c("diffobj-word", "insert")), 916 word.delete=span_f(c("diffobj-word", "delete")), 917 trim=span_f("diffobj-trim"), 918 header=div_f(c("diffobj-header")) 919 ), 920 text=StyleText( 921 gutter.insert=">", 922 gutter.delete="<", 923 gutter.match=" ", 924 line.break="<br />", 925 pad.col="" 926 ), 927 summary=StyleSummaryHtml(), 928 pager=PagerBrowser(), 929 wrap=FALSE, 930 pad=FALSE, 931 nchar.fun=nchar_html, # only used in gutter 932 escape.html.entities=TRUE, 933 na.sub=" ", 934 blank.sub=" ", 935 disp.width=80L, 936 html.output="auto", 937 css="", 938 js="", 939 finalizer=finalizeHtml, 940 scale=TRUE 941 ), 942 validity=function(object) { 943 if(!is.chr.1L(object@css)) 944 return("slot `css` must be character(1L)") 945 if(!is.chr.1L(object@js)) 946 return("slot `js` must be character(1L)") 947 if(!is.chr.1L(object@html.output)) 948 return("slot `html.output` must be character(1L)") 949 if(!is.TF(object@escape.html.entities)) 950 return("slot `escape.html.entities` must be TRUE or FALSE.") 951 if(!is.TF(object@scale)) 952 return("slot `scale` must be TRUE or FALSE.") 953 if(!identical(object@wrap, FALSE)) 954 return("slot `wrap` must be FALSE for `styleHtml` objects.") 955 TRUE 956 } 957) 958# construct with default values specified via options 959 960setMethod("initialize", "StyleHtml", 961 function( 962 .Object, css=getOption("diffobj.html.css"), 963 js=getOption("diffobj.html.js"), 964 html.output=getOption("diffobj.html.output", default='auto'), 965 escape.html.entities=getOption("diffobj.html.escape.html.entities"), 966 scale=getOption("diffobj.html.scale", default=TRUE), 967 ... 968 ) { 969 # Had some problems with R 3.1 where it appears that the initialize methods 970 # are triggered on load, but before `.onLoad` is called, and as such options 971 # are not available. This is why we have this logic here. 972 973 if(is.null(css)) css <- diffobj_css() 974 if(is.null(js)) js <- diffobj_js() 975 976 if(!isTRUE(css.err <- is.one.file.name(css))) 977 stop("Argument `css` ", css.err) 978 if(!isTRUE(js.err <- is.one.file.name(js))) 979 stop("Argument `js` ", js.err) 980 981 if(!is.TF(scale)) 982 stop("Argument `scale` must be TRUE or FALSE") 983 valid.html.output <- c("auto", "page", "diff.only", "diff.w.style") 984 if(!string_in(html.output, valid.html.output)) 985 stop("Argument `html.output` must be in `", dep(valid.html.output), "`.") 986 987 callNextMethod( 988 .Object, css=css, html.output=html.output, js=js, scale=scale, ... 989 ) 990} ) 991#' @export StyleHtmlLightRgb 992#' @exportClass StyleHtmlLightRgb 993#' @rdname Style 994 995StyleHtmlLightRgb <- setClass( 996 "StyleHtmlLightRgb", contains=c("StyleHtml", "Light", "Rgb") 997) 998setMethod("initialize", "StyleHtmlLightRgb", 999 function(.Object, ...) { 1000 .Object@funs@container <- cont_f(c("light", "rgb")) 1001 callNextMethod(.Object, ...) 1002 } 1003) 1004#' @export StyleHtmlLightYb 1005#' @exportClass StyleHtmlLightYb 1006#' @rdname Style 1007 1008StyleHtmlLightYb <- setClass( 1009 "StyleHtmlLightYb", contains=c("StyleHtml", "Light", "Yb"), 1010) 1011setMethod("initialize", "StyleHtmlLightYb", 1012 function(.Object, ...) { 1013 .Object@funs@container <- cont_f(c("light", "yb")) 1014 callNextMethod(.Object, ...) 1015 } 1016) 1017# Helper structure for constructing our defaults array 1018 1019.dfs.dims <- list( 1020 format=c("raw", "ansi8", "ansi256", "html"), 1021 brightness=c("neutral", "light", "dark"), 1022 color.mode=c("rgb", "yb") # add b/w? 1023) 1024.dfs.dims.sizes <- vapply(.dfs.dims, length, integer(1L)) 1025.dfs.arr <- array( 1026 vector("list", prod(.dfs.dims.sizes)), dim=.dfs.dims.sizes, dimnames=.dfs.dims 1027) 1028 1029#' Class for Tracking Default Styles by Style Type 1030#' 1031#' Provides a mechanism for specifying a style based on the style properties 1032#' along dimensions of format, brightness, and color. This allows a user to 1033#' request a style that meets a certain description (e.g. a \dQuote{light} 1034#' scheme in \dQuote{ansi256} format), without having to provide a specific 1035#' \code{\link{Style}} object. 1036#' 1037#' @section An Array of Styles: 1038#' 1039#' A \code{PaletteOfStyles} object is an \dQuote{array} containing either 1040#' \dQuote{classRepresentation} objects that extend \code{StyleHtml} or are 1041#' instances of objects that inherit from \code{StyleHtml}. The \code{diff*} 1042#' methods then pick an object/class from this array based on the values of 1043#' the \code{format}, \code{brightness}, and \code{color.mode} parameters. 1044#' 1045#' For the most part the distinction between actual \code{Style} objects vs 1046#' \dQuote{classRepresentation} ones is academic, except that with the latter 1047#' you can control the instantiation by providing a parameter list as the 1048#' \code{style} argument to the \code{diff*} methods. This is not an option with 1049#' already instantiated objects. See examples. 1050#' 1051#' @section Dimensions: 1052#' 1053#' There are three general orthogonal dimensions of styles that can be used when 1054#' rendering diffs: the type of format, the \dQuote{brightness} of the output, 1055#' and whether the colors used are distinguishable if you assume reds and greens 1056#' are not distinguishable. Defaults for the intersections each of these 1057#' dimensions are encoded as a three dimensional list. This list is just an 1058#' atomic vector of type \dQuote{list} with a length 3 \code{dim} attribute. 1059#' 1060#' The array/list dimensions are: 1061#' \itemize{ 1062#' \item \code{format}: the format type, one of \dQuote{raw}, 1063#' \dQuote{ansi8}, \dQuote{ansi256}, or \dQuote{html} 1064#' \item \code{brightness}: whether the colors are bright or not, which 1065#' allows user to chose a scheme that is compatible with their console, 1066#' one of: \dQuote{light}, \dQuote{dark}, \dQuote{normal} 1067#' \item \code{color.mode}: \dQuote{rgb} for full color or \dQuote{yb} for 1068#' dichromats (yb stands for Yellow Blue). 1069#' } 1070#' 1071#' Each of these dimensions can be specified directly via the corresponding 1072#' parameters to the \code{diff*} methods. 1073#' 1074#' @section Methods: 1075#' 1076#' \code{PaletteOfStyles} objects have The following methods implemented: 1077#' \itemize{ 1078#' \item \code{[}, \code{[<-}, \code{[[} 1079#' \item show 1080#' \item summary 1081#' \item dimnames 1082#' } 1083#' @section Structural Details: 1084#' 1085#' The array/list is stored in the \code{data} slot of 1086#' \code{PaletteOfStyles} objects. Subsetting methods are provided so you 1087#' may operate directly on the S4 object as you would on a regular array. 1088#' 1089#' The array/list must be fully populated with objects that are or inherit 1090#' \code{Style}, or are \dQuote{classRepresentation} objects (i.e. those of 1091#' the type returned by \code{\link{getClassDef}}) that extend \code{Style}. 1092#' By default the array is populated only with \dQuote{classRepresentation} 1093#' objects as that allows the list form of the \code{style} parameter to the 1094#' \code{diff*} methods. If there is a particular combination of coordinates 1095#' that does not have a corresponding defined style a reasonable substitution 1096#' must be provided. For example, this package only defines \dQuote{light} 1097#' HTML styles, so it simply uses that style for all the possible 1098#' \code{brightness} values. 1099#' 1100#' There is no explicit check that the objects in the list comply with the 1101#' descriptions implied by their coordinates, although the default object 1102#' provided by the package does comply for the most part. One check that is 1103#' carried out is that any element that has a \dQuote{html} value in the 1104#' \code{format} dimension extends \code{StyleHtml}. 1105#' 1106#' While the list may only have the three dimensions described, you can add 1107#' values to the dimensions provided the values described above are the first 1108#' ones in each of their corresponding dimensions. For example, if you wanted 1109#' to allow for styles that would render in \code{grid} graphics, you could 1110#' generate a default list with a \dQuote{"grid"} value appended to the values 1111#' of the \code{format} dimension. 1112#' 1113#' @export PaletteOfStyles 1114#' @exportClass PaletteOfStyles 1115#' @examples 1116#' \dontrun{ 1117#' ## Look at all "ansi256" styles (assumes compatible terminal) 1118#' PaletteOfStyles()["ansi256",,] 1119#' } 1120#' ## Generate the default style object palette, and replace 1121#' ## the ansi256 / light / rgb style with our modified one 1122#' ## which for illustrative purposes is the raw style 1123#' my.pal <- PaletteOfStyles() 1124#' my.style <- StyleRaw() # See `?Style` for custom styles 1125#' my.style@funs@word.delete <- function(x) sprintf("--%s--", x) 1126#' my.pal["ansi256", "light", "rgb"] <- list(my.style) # note `list()` 1127#' ## Output has no format now for format/color.mode/brightness 1128#' ## we modified ... 1129#' ## `pager="off"` for CRAN compliance; you may omit in normal use 1130#' diffPrint( 1131#' 1:3, 2:5, format="ansi256", color.mode="rgb", brightness="light", 1132#' palette.of.styles=my.pal, pager="off", disp.width=80 1133#' ) 1134#' ## If so desired, set our new style palette as the default 1135#' ## one; could also pass directly as argument to `diff*` funs 1136#' \dontrun{ 1137#' options(diffobj.palette=defs) 1138#' } 1139 1140PaletteOfStyles <- setClass( 1141 "PaletteOfStyles", 1142 slots=c(data="array"), 1143 validity=function(object) { 1144 dat <- object@data 1145 valid.names <- names(.dfs.dims) 1146 if(!is.list(dat)) 1147 return("Slot `data` must be a dimensioned list") 1148 if( 1149 !is.list(dimnames(dat)) || 1150 !identical(names(dimnames(dat)), valid.names) || 1151 !all(vapply(dimnames(dat), is.character, logical(1L))) || 1152 anyNA(unlist(dat)) 1153 ) 1154 return( 1155 paste0( 1156 "`dimnames` for default styles must be a list with names `", 1157 paste0(deparse(valid.names), collapse=""), "` and contain only ", 1158 "character vectors with no NA values." 1159 ) ) 1160 1161 if( 1162 !all( 1163 vapply( 1164 valid.names, 1165 function(x) identical( 1166 .dfs.dims[[x]], head(dimnames(dat)[[x]], length(.dfs.dims[[x]])) 1167 ), 1168 logical(1L) 1169 ) ) ) 1170 return("Style dimension names do not contain all required values") 1171 1172 # May be either style objects or Style Class definitions 1173 1174 style.def <- getClassDef("Style", package="diffobj") 1175 are.styles <- vapply(dat, is, logical(1L), "Style") 1176 are.styles.def <- logical(length(are.styles)) 1177 are.styles.def[!are.styles] <- vapply( 1178 dat[!are.styles], 1179 function(x) is(x, "classRepresentation") && extends(x, style.def), 1180 logical(1L) 1181 ) 1182 if(!all(are.styles | are.styles.def)) 1183 return( 1184 paste0( 1185 "Styles may only contain objects that inherit from `Style` or class ", 1186 "definitions that extend `Style`" 1187 ) ) 1188 if( 1189 !all( 1190 vapply( 1191 dat["html", ,], 1192 function(x) 1193 is(x, "classRepresentation") && extends(x, "StyleHtml") || 1194 is(x, "StyleHtml"), 1195 logical(1L) 1196 ) 1197 ) 1198 ) 1199 return("Styles classifed as HTML must extend `StyleHtml`") 1200 TRUE 1201 } 1202) 1203setMethod("initialize", "PaletteOfStyles", 1204 function(.Object, ...) { 1205 .dfs.arr["raw", , ] <- list( 1206 getClassDef("StyleRaw", package="diffobj", inherits=FALSE) 1207 ) 1208 .dfs.arr["ansi8", , "rgb"] <- list( 1209 getClassDef("StyleAnsi8NeutralRgb", package="diffobj", inherits=FALSE) 1210 ) 1211 .dfs.arr["ansi8", , "yb"] <- list( 1212 getClassDef("StyleAnsi8NeutralYb", package="diffobj", inherits=FALSE) 1213 ) 1214 .dfs.arr["ansi256", "neutral", "rgb"] <- list( 1215 getClassDef("StyleAnsi8NeutralRgb", package="diffobj", inherits=FALSE) 1216 ) 1217 .dfs.arr["ansi256", "neutral", "yb"] <- list( 1218 getClassDef("StyleAnsi8NeutralYb", package="diffobj", inherits=FALSE) 1219 ) 1220 .dfs.arr["ansi256", "light", "rgb"] <- list( 1221 getClassDef("StyleAnsi256LightRgb", package="diffobj", inherits=FALSE) 1222 ) 1223 .dfs.arr["ansi256", "light", "yb"] <- list( 1224 getClassDef("StyleAnsi256LightYb", package="diffobj", inherits=FALSE) 1225 ) 1226 .dfs.arr["ansi256", "dark", "rgb"] <- list( 1227 getClassDef("StyleAnsi256DarkRgb", package="diffobj", inherits=FALSE) 1228 ) 1229 .dfs.arr["ansi256", "dark", "yb"] <- list( 1230 getClassDef("StyleAnsi256DarkYb", package="diffobj", inherits=FALSE) 1231 ) 1232 .dfs.arr["html", , "rgb"] <- list( 1233 getClassDef("StyleHtmlLightRgb", package="diffobj", inherits=FALSE) 1234 ) 1235 .dfs.arr["html", , "yb"] <- list( 1236 getClassDef("StyleHtmlLightYb", package="diffobj", inherits=FALSE) 1237 ) 1238 .Object@data <- .dfs.arr 1239 callNextMethod(.Object, ...) 1240 } 1241) 1242#' @rdname Extract_PaletteOfStyles 1243 1244setReplaceMethod( 1245 "[", signature=c(x="PaletteOfStyles"), 1246 function(x, i, j, ..., value) { 1247 x@data[i, j, ...] <- value 1248 validObject(x) 1249 x 1250} ) 1251#' @rdname Extract_PaletteOfStyles 1252 1253setMethod( 1254 "[", signature=c(x="PaletteOfStyles"), 1255 function(x, i, j, ..., drop=FALSE) { 1256 x@data <- x@data[i, j, ..., drop=drop] 1257 x 1258 } 1259) 1260#' Extract/Replace a Style Class or Object from PaletteOfStyles 1261#' 1262#' @rdname Extract_PaletteOfStyles 1263#' @seealso \code{\link{diffPrint}} for explanations of \code{format}, 1264#' \code{brightness}, and \code{color.mode} 1265#' @param x a \code{\link{PaletteOfStyles}} object 1266#' @param i numeric, or character corresponding to a valid style \code{format} 1267#' @param j numeric, or character corresponding to a valid style 1268#' \code{brightness} 1269#' @param ... pass a numeric or character corresponding to a valid 1270#' \code{color.mode} 1271#' @param exact passed on to generic 1272#' @param drop TRUE or FALSE, whether to drop dimensions, defaults to FALSE, 1273#' which is different than generic 1274#' @param value a \emph{list} of \code{\link{Style}} class or 1275#' \code{\link{Style}} objects 1276#' @return a \code{\link{Style}} \code{ClassRepresentation} object or 1277#' \code{\link{Style}} object for \code{[[}, and a list of the same for 1278#' \code{[} 1279#' @examples 1280#' pal <- PaletteOfStyles() 1281#' pal[["ansi256", "light", "rgb"]] 1282#' pal["ansi256", "light", ] 1283#' pal["ansi256", "light", "rgb"] <- list(StyleAnsi8NeutralRgb()) 1284 1285setMethod( 1286 "[[", signature=c(x="PaletteOfStyles"), 1287 function(x, i, j, ..., exact=TRUE) { 1288 x@data[[i, j, ..., exact=exact]] 1289 } 1290) 1291#' Retrieve Dimnames for PaletteOfStyles Objects 1292#' 1293#' @param x a \code{\link{PaletteOfStyles}} object 1294#' @return list the dimension names 1295#' dimnames(PaletteOfStyles()) 1296 1297setMethod("dimnames", "PaletteOfStyles", function(x) dimnames(x@data)) 1298 1299# Matrices used for show methods for styles 1300 1301.mx1 <- .mx2 <- matrix(1:50, ncol=2) 1302.mx2[c(6L, 40L)] <- 99L 1303.mx2 <- .mx2[-7L,] 1304 1305#' Show Method for Style Objects 1306#' 1307#' Display a small sample diff with the Style object styles applied. For 1308#' ANSI light and dark styles, will also temporarily set the background and 1309#' foreground colors to ensure they are compatible with the style, even though 1310#' this is not done in normal output (i.e. if you intend on using a 1311#' \dQuote{light} style, you should set your terminal background color to be 1312#' light or expect sub-optimal rendering). 1313#' 1314#' @param object a \code{Style} S4 object 1315#' @return NULL, invisibly 1316#' @examples 1317#' show(StyleAnsi256LightYb()) # assumes ANSI colors supported 1318 1319setMethod("show", "Style", 1320 function(object) { 1321 cat(sprintf("Object of class `%s`:\n\n", class(object))) 1322 d.p <- diffPrint( 1323 .mx1, .mx2, context=1, line.limit=7L, 1324 style=object, pager=PagerOff(), 1325 tar.banner="diffobj:::.mx1", cur.banner="diffobj:::.mx2", 1326 sgr.supported=if(is(object, "Ansi")) TRUE 1327 ) 1328 d.txt <- capture.output(show(d.p)) 1329 if(is(object, "Ansi")) { 1330 old.opt <- options(crayon.enabled=TRUE) 1331 on.exit(options(old.opt)) 1332 pad.width <- max(nchar2(d.txt, sgr.supported=TRUE)) 1333 d.txt <- rpad(d.txt, width=pad.width, sgr.supported=TRUE) 1334 bgWhite <- crayon::make_style(rgb(1, 1, 1), bg=TRUE, colors=256) 1335 white <- crayon::make_style(rgb(1, 1, 1), colors=256) 1336 if(is(object, "Light")) { 1337 d.txt <- bgWhite(crayon::black(d.txt)) 1338 } else if (is(object, "Dark")) { 1339 d.txt <- crayon::bgBlack(white(d.txt)) 1340 } 1341 options(old.opt) 1342 on.exit(NULL) 1343 if(is(object, "Light") || is(object, "Dark")) { 1344 d.txt <- c( 1345 d.txt, "", 1346 strwrap( 1347 paste0( 1348 "Default bg and fg colors forced to appropriate colors for ", 1349 "scheme; this does not happen in actual use." 1350 ), 1351 width=pad.width + 20L 1352 ) ) } } 1353 cat(d.txt, sep="\n") 1354 invisible(NULL) 1355} ) 1356#' @rdname show-Style-method 1357 1358setMethod("show", "StyleHtml", 1359 function(object) { 1360 cat(sprintf("Class `%s` sample output:\n\n", class(object))) 1361 cat("[Object Renders in HTML]\n") 1362 invisible(NULL) 1363} ) 1364#' Display a PaletteOfStyles 1365#' 1366#' @param object a \code{\link{PaletteOfStyles}} object 1367#' @return NULL, invisibly 1368 1369setMethod("show", "PaletteOfStyles", 1370 function(object) { 1371 fmt <- dimnames(object)$format 1372 brt <- dimnames(object)$brightness 1373 clr <- dimnames(object)$color.mode 1374 1375 for(f in fmt) { 1376 for(b in brt) { 1377 for(c in clr) { 1378 obj <- object[[f, b, c]] 1379 if(is(obj, "classRepresentation")) obj <- new(obj) 1380 txt <- capture.output(show(obj)) 1381 cat( 1382 sprintf("\nformat: %s, brightness: %s, color.mode: %s\n\n", f, b, c) 1383 ) 1384 cat(paste0(" ", txt), sep="\n") 1385} } } } ) 1386#' Display a Summarized Version of a PaletteOfStyles 1387#' 1388#' @param object a \code{\link{PaletteOfStyles}} object 1389#' @param ... unused, for compatibility with generic 1390#' @return character representation showing classes and/or objects in 1391#' PaletteOfStyles 1392#' summary(PaletteOfStyles()) 1393 1394setMethod("summary", "PaletteOfStyles", 1395 function(object, ...) 1396 apply( 1397 object@data, 1398 1:3, 1399 function(x) 1400 if(is(x[[1L]], "classRepresentation")) 1401 paste0("class: ", x[[1L]]@className) else 1402 paste0("object: ", class(x[[1L]])) 1403 ) 1404) 1405 1406# Helper function to render output for vignette 1407 1408display_ansi_256_styles <- function() { 1409 styles <- lapply( 1410 list( 1411 StyleAnsi8NeutralYb(), StyleAnsi8NeutralRgb(), 1412 StyleAnsi256DarkYb(), StyleAnsi256DarkRgb(), 1413 StyleAnsi256LightYb(), StyleAnsi256LightRgb() 1414 ), 1415 function(x) capture.output(show(x))[3:9] 1416 ) 1417 names <- c("Neutral", "Dark", "Light") 1418 cat("\n") 1419 lapply( 1420 1:3, 1421 function(x) { 1422 cat(paste0(" ", names[x]), "\n\n") 1423 cat(paste(" ", styles[[x * 2 - 1]], " ", styles[[x * 2]]), sep="\n") 1424 cat("\n") 1425 } 1426 ) 1427 invisible(NULL) 1428} 1429