Skip to content

Commit

Permalink
add hl_str_diff() and hl_grep_*()
Browse files Browse the repository at this point in the history
  • Loading branch information
coolbutuseless committed Aug 30, 2023
1 parent 241e8a5 commit f9f7627
Show file tree
Hide file tree
Showing 29 changed files with 902 additions and 177 deletions.
76 changes: 21 additions & 55 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag.
# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches:
- main
- master
branches: [main, master]
pull_request:
branches:
- main
- master
branches: [main, master]

name: R-CMD-check

Expand All @@ -22,62 +18,32 @@ jobs:
fail-fast: false
matrix:
config:
- {os: macos-latest, r: 'release'}
- {os: windows-latest, r: 'release'}
- {os: macOS-latest, r: 'release'}
- {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"}
- {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"}
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'oldrel-1'}

env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
RSPM: ${{ matrix.config.rspm }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_KEEP_PKG_SOURCE: yes

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: r-lib/actions/setup-r@v1
- uses: r-lib/actions/setup-pandoc@v2

- uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
http-user-agent: ${{ matrix.config.http-user-agent }}
use-public-rspm: true

- uses: r-lib/actions/setup-pandoc@v1

- name: Query dependencies
run: |
install.packages('remotes')
saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
shell: Rscript {0}

- name: Cache R packages
if: runner.os != 'Windows'
uses: actions/cache@v2
- uses: r-lib/actions/setup-r-dependencies@v2
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-

- name: Install system dependencies
if: runner.os == 'Linux'
run: |
while read -r cmd
do
eval sudo $cmd
done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))')
- name: Install dependencies
run: |
remotes::install_deps(dependencies = TRUE)
remotes::install_cran("rcmdcheck")
shell: Rscript {0}

- name: Check
env:
_R_CHECK_CRAN_INCOMING_REMOTE_: false
run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
shell: Rscript {0}
extra-packages: any::rcmdcheck
needs: check

- name: Upload check results
if: failure()
uses: actions/upload-artifact@main
- uses: r-lib/actions/check-r-package@v2
with:
name: ${{ runner.os }}-r${{ matrix.config.r }}-results
path: check
upload-snapshots: true
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: emphatic
Type: Package
Title: ANSI Colouring of Data.Frames, Matrices and Atomic Vectors
Version: 0.1.4
Version: 0.1.5
Author: mikefc
Maintainer: mikefc <[email protected]>
Description: Colour data.frame, matrix and vector output in the console.
Expand All @@ -10,7 +10,7 @@ BugReports: https://github.com/coolbutuseless/emphatic/issues
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
RoxygenNote: 7.1.1
RoxygenNote: 7.2.3
Depends:
R (>= 2.10)
Suggests:
Expand Down
10 changes: 10 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@ export(as_emphatic)
export(as_html)
export(as_svg)
export(hl)
export(hl_grep)
export(hl_grep_character)
export(hl_grep_deparse)
export(hl_grep_print)
export(hl_grep_str)
export(hl_loc)
export(hl_mat)
export(hl_opt)
export(hl_opt_global)
export(hl_str_diff)
export(hl_vec)
export(is_emphatic)
export(knit_print.emphatic)
importFrom(grDevices,col2rgb)
importFrom(grDevices,convertColor)
importFrom(grDevices,rgb)
importFrom(utils,adist)
importFrom(utils,capture.output)
importFrom(utils,head)
importFrom(utils,modifyList)
importFrom(utils,str)
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@

# emphatic 0.1.5 2023-08-30

* Added `hl_str_diff()` to highlight differences between two strings.
* Added functions for highlighting the results of a `grep()`
* `hl_grep()`
* `hl_str()`
* `hl_character()`
* `hl_print()`
* `hl_deparse()`

# emphatic 0.1.4 2020-11-13

* Fix for rendering of logical atomic vectors
Expand Down
4 changes: 2 additions & 2 deletions R/core-html.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
col2hex <- function(colours) {

colours <- col2rgb(colours)
colours <- as.character(as.hexmode(colours))
colours <- grDevices::col2rgb(colours)
colours <- structure(sprintf("%02x", colours), dim = dim(colours))
colours <- apply(colours, 2, paste0, collapse = '')

paste0('#', colours)
Expand Down
51 changes: 42 additions & 9 deletions R/core.R
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ get_legends <- function(.data) {
#' @export
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
is_emphatic <- function(x) {

if (is.list(x) && inherits(x, 'emphatic')) {
return(TRUE);
}

text <- get_colour_matrix(x, 'text')
fill <- get_colour_matrix(x, 'fill')

Expand Down Expand Up @@ -137,6 +142,20 @@ as.character.emphatic <- function(x, ..., mode = 'ansi') {
# Sanity check
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stopifnot(is_emphatic(x))

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Lists of emphatic objects
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (is.list(x) && !is.data.frame(x)) {
strs <- vapply(x, as.character, character(1), ..., mode = mode)
if (mode == 'ansi') {
return (paste(strs, collapse = "\n"))
} else {
return (paste(strs, collapse = "<br/>"))
}
}


stopifnot(mode %in% c('ansi', 'html'))

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -170,13 +189,17 @@ as.character.emphatic <- function(x, ..., mode = 'ansi') {
if (is_atomic(x)) {
fx <- x
if ((!is.double(fx) && !is.integer(fx) && !is.raw(fx) && !is.complex(fx) && !is.logical(fx)) || inherits(fx, 'Date')) {
fx <- dQuote(fx, FALSE)
if (!inherits(x, 'compact')) {
fx <- dQuote(fx, FALSE)
}
}
if (!inherits(x, 'compact')) {
fx <- format(fx)
}
fx <- format(fx)
mat <- matrix(fx, nrow = 1)
if (!is.null(names(x))) {
colnames(mat) <- names(x)
} else {
} else if (!inherits(x, 'compact')) {
mat[] <- paste0(" ", mat) # need padding if no column names
}
} else {
Expand Down Expand Up @@ -229,7 +252,8 @@ as.character.emphatic <- function(x, ..., mode = 'ansi') {
dark_mode = opt$dark_mode,
underline_header = opt$underline_header,
mode = mode,
atomic = is_atomic(x)
atomic = is_atomic(x),
compact = inherits(x, 'compact')
)

res
Expand Down Expand Up @@ -262,7 +286,8 @@ as_character_inner <- function(m,
dark_mode = TRUE,
underline_header = TRUE,
mode = 'ansi',
atomic = FALSE) {
atomic = FALSE,
compact = FALSE) {


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -274,7 +299,7 @@ as_character_inner <- function(m,
stopifnot(mode %in% c('ansi', 'html'))

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Automatic constrasting text for foreground?
# Automatic contrasting text for foreground?
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (text_mode == 'contrast') {
new_text <- calc_contrasting_text(fill, text_contrast = text_contrast, dark_mode = dark_mode)
Expand Down Expand Up @@ -389,7 +414,7 @@ as_character_inner <- function(m,
if (has_col_names) {
row_idx <- rep('', length(chunks))
} else {
fmt <- paste0("[%", max_row_idx_digits, 'i]')
fmt <- paste0("[%", max_row_idx_digits, 'i] ')
row_idx <- sprintf(fmt, (seq_along(chunks) - 1) * n_per_line + 1)
}

Expand All @@ -403,10 +428,18 @@ as_character_inner <- function(m,
}

new_row <- paste(ansi_mat[chunk_idx], collapse = '')
new_row <- paste0(row_idx[i], new_row)
if (!compact) {
# Add '[1]' in front of row
new_row <- paste0(row_idx[i], new_row)
}
res <- c(res, new_row)
}
res <- paste(res, collapse = "\n")

if (!compact) {
res <- paste(res, collapse = "\n")
} else {
res <- paste0(res, collapse = '')
}
} else {


Expand Down
109 changes: 109 additions & 0 deletions R/str_diff.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Colour the differences between two strings
#'
#' Highlight the differences between two strings in
#' terms of substitutions, insertions and deletions.
#'
#' @param x,y each argument is a single string. vectors of strings not currently
#' supported.
#' @param fs,bs,fi,bi,fd,bd the (f)oreground and (b)ackground colouring for
#' string (s)ubstitutions, (i)insertions and (d)eletions.
#' @param ... further arguments passed to \code{adist()}
#'
#' @return list of 'emphatic' objects which could be renderd to ANSI or HTML
#'
#' @importFrom grDevices col2rgb
#' @importFrom utils adist
#' @importFrom utils head
#' @export
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
hl_str_diff <- function(x, y,
bs = 'dodgerblue', bi = 'darkgreen', bd = 'firebrick',
fs = 'white' , fi = 'white' , fd = 'white',
...) {

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Sanity check
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stopifnot(
is.character(x),
is.character(y),
!is.na(x),
!is.na(y),
length(x) == 1,
length(y) == 1
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Calculate the levenshtein distance and the transformation sequence
# to turn the first string into the second.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lev <- utils::adist(x, y, counts = TRUE, ...)
lev <- attr(lev, 'trafos')[1]
lev <- strsplit(lev, '')[[1]]


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Adjust input strings to account for deletions and insertions
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
xbits <- strsplit(x , '')[[1]]
ybits <- strsplit(y , '')[[1]]
dels <- which(lev == 'D')
for (del in dels) {
if (del == 1) {
ybits <- c(' ', ybits)
} else if (del > length(ybits)) {
ybits <- c(ybits, ' ')
} else {
lower <- seq(1, (del - 1))
upper <- seq(del, length(ybits))
ybits <- c(ybits[lower], ' ', ybits[upper])
}
}

inserts <- which(lev == 'I')
for (ins in inserts) {
if (ins == 1) {
xbits <- c(' ', xbits)
} else if (ins > length(xbits)) {
xbits <- c(xbits, ' ')
} else {
lower <- seq(1, (ins - 1))
upper <- seq(ins, length(xbits))
xbits <- c(xbits[lower], ' ', xbits[upper])
}
}


rl <- base::rle(lev)
N <- length(rl$values)
end <- cumsum(rl$lengths)
begin <- c(0, head(end, -1) + 1)

xv <- character(N)
yv <- character(N)
for (i in seq(N)) {
xv[i] <- paste(xbits[begin[i]:end[i]], collapse = "")
yv[i] <- paste(ybits[begin[i]:end[i]], collapse = "")
}

xtext <- c(S = fs, I = fi, D = fd, M = NA)[rl$values]
xfill <- c(S = bs, I = bi, D = bd, M = NA)[rl$values]
ytext <- c(S = fs, I = fi, D = fd, M = NA)[rl$values]
yfill <- c(S = bs, I = bi, D = bd, M = NA)[rl$values]

structure(
list(
structure(xv, text = t(as.matrix(xtext)), fill = t(as.matrix(xfill)), class = c("emphatic", "compact")),
structure(yv, text = t(as.matrix(ytext)), fill = t(as.matrix(yfill)), class = c("emphatic", "compact"))
),
class = "emphatic"
)
}






Loading

0 comments on commit f9f7627

Please sign in to comment.