formatR/0000755000176200001440000000000014055461351011670 5ustar liggesusersformatR/NAMESPACE0000644000176200001440000000026214053623112013100 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(tidy_app) export(tidy_dir) export(tidy_eval) export(tidy_file) export(tidy_source) export(usage) import(stats) import(utils) formatR/man/0000755000176200001440000000000014024176674012452 5ustar liggesusersformatR/man/tidy_app.Rd0000644000176200001440000000062614053623112014540 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/shiny.R \name{tidy_app} \alias{tidy_app} \title{A Shiny app to format R code} \usage{ tidy_app() } \description{ Run a Shiny app that formats R code via \code{\link{tidy_source}()}. This app uses input widgets, such as checkboxes, to pass arguments to \code{tidy_source()}. } \examples{ if (interactive()) formatR::tidy_app() } formatR/man/usage.Rd0000644000176200001440000000365314053623112014036 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/usage.R \name{usage} \alias{usage} \title{Show the usage of a function} \usage{ usage( FUN, width = getOption("width"), tidy = TRUE, output = TRUE, indent.by.FUN = FALSE, fail = c("warn", "stop", "none") ) } \arguments{ \item{FUN}{The function name.} \item{width}{The width of the output.} \item{tidy}{Whether to reformat the usage code.} \item{output}{Whether to print the output to the console (via \code{\link{cat}()}).} \item{indent.by.FUN}{Whether to indent subsequent lines by the width of the function name (see \dQuote{Details}).} \item{fail}{A character string that represents the action taken when the width constraint is unfulfillable. "warn" and "stop" will signal warnings and errors, while "none" will do nothing.} } \value{ Reformatted usage code of a function, in character strings (invisible). } \description{ Print the reformatted usage of a function. The arguments of the function are searched by \code{\link{argsAnywhere}()}, so the function can be either exported or non-exported from a package. S3 methods will be marked. } \details{ Line breaks in the output occur between arguments. In particular, default values of arguments will not be split across lines. When \code{indent.by.FUN} is \code{FALSE}, indentation is set by the option \code{\link{getOption}("formatR.indent", 4L)}, the default value of the \code{indent} argument of \code{\link{tidy_source}()}. } \examples{ library(formatR) usage(var) usage(plot) usage(plot.default) # default method usage("plot.lm") # on the 'lm' class usage(usage) usage(barplot.default, width = 60) # output lines have 60 characters or less # indent by width of 'barplot(' usage(barplot.default, width = 60, indent.by.FUN = TRUE) \dontrun{ # a warning is raised because the width constraint is unfulfillable usage(barplot.default, width = 30) } } \seealso{ \code{\link{tidy_source}()} } formatR/man/tidy_eval.Rd0000644000176200001440000000236714053623112014713 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/eval.R \name{tidy_eval} \alias{tidy_eval} \title{Insert output to source code} \usage{ tidy_eval( source = "clipboard", ..., file = "", prefix = "## ", envir = parent.frame() ) } \arguments{ \item{source}{The input file name (by default the clipboard; see \code{\link{tidy_source}()}).} \item{...}{Other arguments passed to \code{\link{tidy_source}()}.} \item{file}{The file name to write to via \code{\link{cat}()}.} \item{prefix}{The prefix to mask the output.} \item{envir}{The environment in which to evaluate the code. By default the parent frame; set \code{envir = NULL} or \code{envir = new.env()} to avoid the possibility of contaminating the parent frame.} } \value{ Evaluated R code with corresponding output (printed on screen or written to a file). } \description{ Evaluate R code by chunks, then insert the output to each chunk. As the output is masked in comments, the source code will not break. } \examples{ library(formatR) ## evaluate simple code as a character vector tidy_eval(text = c("a<-1+1;a", "matrix(rnorm(10),5)")) ## evaluate a file tidy_eval(system.file("format", "messy.R", package = "formatR")) } \references{ \url{https://yihui.org/formatR/} } formatR/man/tidy_dir.Rd0000644000176200001440000000235014053623112014532 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tidy.R \name{tidy_dir} \alias{tidy_dir} \alias{tidy_file} \title{Format all R scripts under a directory, or specified R scripts} \usage{ tidy_dir(path = ".", recursive = FALSE, ...) tidy_file(file, ...) } \arguments{ \item{path}{The path to a directory containning R scripts.} \item{recursive}{Whether to recursively look for R scripts under \code{path}.} \item{...}{Other arguments to be passed to \code{\link{tidy_source}()}.} \item{file}{A vector of filenames.} } \value{ Invisible \code{NULL}. } \description{ Look for all R scripts under a directory (using the pattern \code{"[.][RrSsQq]$"}), then tidy them with \code{\link{tidy_source}()}. If successful, the original scripts will be overwritten with reformatted ones. Please back up the original directory first if you do not fully understand the tricks used by \code{\link{tidy_source}()}. \code{tidy_file()} formats scripts specified by file names. } \examples{ library(formatR) path = tempdir() file.copy(system.file("demo", package = "base"), path, recursive = TRUE) tidy_dir(path, recursive = TRUE) } \seealso{ \code{\link{tidy_source}()} } \author{ Yihui Xie (\code{tidy_dir}) and Ed Lee (\code{tidy_file}) } formatR/man/tidy_source.Rd0000644000176200001440000001136514053623112015262 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tidy.R \name{tidy_source} \alias{tidy_source} \title{Reformat R code while preserving blank lines and comments} \usage{ tidy_source( source = "clipboard", comment = getOption("formatR.comment", TRUE), blank = getOption("formatR.blank", TRUE), arrow = getOption("formatR.arrow", FALSE), brace.newline = getOption("formatR.brace.newline", FALSE), indent = getOption("formatR.indent", 4), wrap = getOption("formatR.wrap", TRUE), width.cutoff = getOption("formatR.width", getOption("width")), args.newline = getOption("formatR.args.newline", FALSE), output = TRUE, text = NULL, ... ) } \arguments{ \item{source}{A character string: file path to the source code (defaults to the clipboard).} \item{comment}{Whether to keep comments.} \item{blank}{Whether to keep blank lines.} \item{arrow}{Whether to replace the assign operator \code{=} with \code{<-}.} \item{brace.newline}{Whether to put the left brace \code{\{} to a new line.} \item{indent}{Number of spaces to indent the code.} \item{wrap}{Whether to wrap comments to the linewidth determined by \code{width.cutoff} (roxygen comments will never be wrapped).} \item{width.cutoff}{An integer in \code{[20, 500]}: if a line's character length is at or over this number, the function will try to break it into a new line. In other words, this is the \emph{lower bound} of the line width. See \sQuote{Details} if an upper bound is desired instead.} \item{args.newline}{Whether to start the arguments of a function call on a new line instead of after the function name and \code{(} when the arguments cannot fit one line.} \item{output}{Whether to output to the console or a file using \code{\link{cat}()}.} \item{text}{An alternative way to specify the input: if \code{NULL}, the function will use the \code{source} argument; if a character vector containing the source code, the function will use this and ignore the \code{source} argument.} \item{...}{Other arguments passed to \code{\link{cat}()}, e.g. \code{file} (this can be useful for batch-processing R scripts, e.g. \code{tidy_source(source = 'input.R', file = 'output.R')}).} } \value{ A list with components \item{text.tidy}{the reformatted code as a character vector} \item{text.mask}{the code containing comments, which are masked in assignments or with the weird operator}. } \description{ Read R code from a file or the clipboard and reformat it. That this function preserves blank lines and comments is a different behavior from that of \code{\link{parse}()} and \code{\link{deparse}()}. This function can also replace \code{=} with \code{<-} where \code{=} means assignment, and re-indent code with a specified number of spaces. } \details{ Value of the argument \code{width.cutoff} wrapped in \code{\link{I}()} (e.g., \code{I(60)}) will be treated as the \emph{upper bound} of the line width. The corresponding argument to \code{deparse()} is a lower bound, so the function will perform a binary search for a width value that can make \code{deparse()} return code with line width smaller than or equal to the \code{width.cutoff} value. If the search fails, a warning will signal, suppressible by global option \code{options(formatR.width.warning = FALSE)}. } \note{ Be sure to read the reference to know other limitations. } \examples{ library(formatR) ## a messy R script messy = system.file("format", "messy.R", package = "formatR") tidy_source(messy) ## use the 'text' argument src = readLines(messy) ## source code cat(src, sep = "\n") ## the formatted version tidy_source(text = src) ## preserve blank lines tidy_source(text = src, blank = TRUE) ## indent with 2 spaces tidy_source(text = src, indent = 2) ## discard comments! tidy_source(text = src, comment = FALSE) ## wanna see the gory truth?? tidy_source(text = src, output = FALSE)$text.mask ## tidy up the source code of image demo x = file.path(system.file(package = "graphics"), "demo", "image.R") # to console tidy_source(x) # to a file f = tempfile() tidy_source(x, blank = TRUE, file = f) ## check the original code here and see the difference file.show(x) file.show(f) ## use global options options(comment = TRUE, blank = FALSE) tidy_source(x) ## if you've copied R code into the clipboard if (interactive()) { tidy_source("clipboard") ## write into clipboard again tidy_source("clipboard", file = "clipboard") } ## the if-else structure tidy_source(text = c("{if(TRUE)1 else 2; if(FALSE){1+1", "## comments", "} else 2}")) } \references{ \url{https://yihui.org/formatR/} (an introduction to this package, with examples and further notes) } \seealso{ \code{\link{parse}()}, \code{\link{deparse}()} } \author{ Yihui Xie <\url{https://yihui.org}> with substantial contribution from Yixuan Qiu <\url{https://yixuan.blog}> } formatR/DESCRIPTION0000644000176200001440000000242714055461351013403 0ustar liggesusersPackage: formatR Type: Package Title: Format R Code Automatically Version: 1.11 Authors@R: c( person("Yihui", "Xie", role = c("aut", "cre"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")), person("Ed", "Lee", role = "ctb"), person("Eugene", "Ha", role = "ctb"), person("Kohske", "Takahashi", role = "ctb"), person("Pavel", "Krivitsky", role = "ctb"), person() ) Description: Provides a function tidy_source() to format R source code. Spaces and indent will be added to the code automatically, and comments will be preserved under certain conditions, so that R code will be more human-readable and tidy. There is also a Shiny app as a user interface in this package (see tidy_app()). Depends: R (>= 3.2.3) Suggests: codetools, shiny, testit, rmarkdown, knitr License: GPL URL: https://github.com/yihui/formatR BugReports: https://github.com/yihui/formatR/issues VignetteBuilder: knitr RoxygenNote: 7.1.1 Encoding: UTF-8 NeedsCompilation: no Packaged: 2021-06-01 13:58:06 UTC; yihui Author: Yihui Xie [aut, cre] (), Ed Lee [ctb], Eugene Ha [ctb], Kohske Takahashi [ctb], Pavel Krivitsky [ctb] Maintainer: Yihui Xie Repository: CRAN Date/Publication: 2021-06-01 16:40:09 UTC formatR/build/0000755000176200001440000000000014055436356012776 5ustar liggesusersformatR/build/vignette.rds0000644000176200001440000000032614055436356015336 0ustar liggesusersb```b`a@&0`b fd`aiE%AzA)hRRy y%E)%y % Ph*y`dq-:C,Q,LH YsSt楀aM wjey~L6̜T!%ps QY_/( @hrNb1GRKҊASȥformatR/tests/0000755000176200001440000000000014053622713013031 5ustar liggesusersformatR/tests/test-all.R0000644000176200001440000000004412235532745014704 0ustar liggesuserslibrary(testit) test_pkg('formatR') formatR/tests/testit/0000755000176200001440000000000014053517255014351 5ustar liggesusersformatR/tests/testit/test-utils.R0000644000176200001440000000172514053547321016613 0ustar liggesuserslibrary(testit) assert('move_leftbrace() works', { # no indent before abc, so no indent before { (move_leftbrace(c('abc() {', ' }')) %==% c('abc()', '{', ' }')) # 3 spaces before abc, 3 before { (move_leftbrace(c(' a() {', '}')) %==% c(' a()', ' {', '}')) (move_leftbrace(rep(c(' a() {', '}'), 5)) %==% rep(c(' a()', ' {', '}'), 5)) # blank lines are not removed (move_leftbrace(c('a', '', 'b')) %==% c('a', '', 'b')) (move_leftbrace(c('if (TRUE) {', ' if (FALSE) {', ' 1', ' }', '}')) %==% c('if (TRUE)', '{', ' if (FALSE)', ' {', ' 1', ' }', '}')) (move_leftbrace(c('if (TRUE) {', ' 1', '} else {', ' 2}')) %==% c('if (TRUE)', '{', ' 1', '} else', '{', ' 2}')) }) assert('reindent_lines() works', { (reindent_lines('') %==% '') (reindent_lines(c('', '')) %==% c('', '')) (reindent_lines(' ', n = 2) %==% ' ') (reindent_lines(c('if (TRUE) {', ' 1', '}'), n = 2) %==% c('if (TRUE) {', ' 1', '}')) }) formatR/tests/testit/test-usage.R0000644000176200001440000001447614052075675016575 0ustar liggesuserslibrary(testit) capture_usage = function(...) capture.output(usage(..., output = TRUE)) n_spaces = function(n) paste(character(n + 1L), collapse = ' ') # Test usage() ------------------------------------------------------------ make_fn = function(arg) { eval(call('function', as.pairlist(arg), quote(expr = ))) } subsets_lgl = function(n) { lapply(seq_len(2L ^ n) - 1L, function(.) as.logical(intToBits(.))[1L:n]) } subsets = function(x) { lapply(subsets_lgl(length(x)), function(.) x[.]) } args_pre = alist(x1 = , x2 = , x3 = , x4 = , y1 = 1, y2 = 2, y3 = 3, y4 = 4) args_post = alist(a = '', b = 'b') args_wo_dots = subsets(args_pre) args_w_dots = do.call('c', lapply(subsets(args_post), function(.) { lapply(args_wo_dots, function(..) c(.., alist(... = ), .)) })) args = c(args_wo_dots, args_w_dots) fns = lapply(args, make_fn) # test usage() for 7.5k+ functions assert('usage() output respects indent and line width, whenver this is feasible', { ops = options(formatR.indent = 4L) re = sprintf('^%s\\S', n_spaces(getOption('formatR.indent'))) w0 = nchar('a_function()') # call with maximal set of arguments usg = paste(trimws( deparse(make_fn(c(args_pre, alist(... = ), args_post))), which = 'left'), collapse = '') w1 = nchar(sub('^function ', 'a_function', usg)) assertions = lapply(fns, function(f) { a_function = f r = (w1 - w0) %/% 4L lapply(seq(w0, w1 + r, by = r), function(w) { out = capture_usage(a_function, w) c( 'output was created' = length(out) > 0L, 'lines within width' = nchar(out) <= w, 'indentation by indent amt' = grepl(re, out[-1L]) ) }) }) options(ops) (unlist(assertions)) }) assert('for an S3 method, usage() uses the generic function name in call signature', { out = capture_usage(barplot.default, 60L) (out[1L] %==% '## Default S3 method:') (substr(out[2L], 1L, 8L) %==% 'barplot(') }) assert('if width constraint is unfulfillable, usage() warns when fail is "warn"', { # verify that width constraint is unfulfillable out = suppressWarnings(capture_usage(barplot.default, 30L, fail = 'warn')) (any(nchar(out) > 30L)) (has_warning(capture_usage(barplot.default, 30L, fail = 'warn'))) }) assert('if width constraint is unfulfillable, usage() stops when fail is "stop"', { out = tryCatch(capture_usage(barplot.default, 30L, fail = 'stop'), error = identity) (inherits(out, 'error')) }) assert('if width constraint is unfulfillable, usage() is silent when fail is "none"', { out = tryCatch( capture_usage(barplot.default, 30L, fail = 'none'), warning = identity, error = identity ) (!inherits(out, c('error', 'warning'))) }) assert('if width constraint is unfulfillable and fail is "warn" or "stop", the lengths of all overflowing lines are shown', { out = capture.output( suppressWarnings(usage(barplot.default, 30L, fail = 'warn')) ) warn = capture.output(cat(tryCatch( usage(barplot.default, 30L, fail = 'warn'), warning = conditionMessage )))[-1L] bad_lines = out[nchar(out) > 30L] overflow_out = nchar(bad_lines) overflow_warn = as.integer(sub('^\\(([[:digit:]]*)\\).*', '\\1', warn)) (overflow_out %==% overflow_warn) }) assert('usage() fits entire call on one line if it falls within width', { foo = function(bar, ..., baz = "baz") {} width = nchar('foo(bar, ..., baz = "baz")') (vapply(seq(width, width + 60L, by = 5L), function(w) { out = usage(foo, width = w, output = FALSE) nchar(out) %==% width }, logical(1))) }) assert('usage() breaks lines maximally and uniformly when all lines of same length', { foo = function(bar, baz = 0, buzz, x, ..., y = 2, z = 3) {} w = nchar('foo(bar, baz = 0,') out = capture_usage(foo, width = w, indent.by.FUN = TRUE) (length(out) %==% 3L) (all(nchar(out) == w)) }) assert('usage() indents by getOption("formatR.indent", 4L), when indent.by.FUN is FALSE', { foo = function(bar, ..., baz = "baz") {} ops = options(formatR.indent = NULL) out = capture_usage(foo, width = 20L, indent.by.FUN = FALSE) options(ops) (out[2L] %==% ' baz = "baz")') ops = options(formatR.indent = 2L) out = capture_usage(foo, width = 20L) options(ops) (out[2L] %==% ' baz = "baz")') }) assert('usage() indents by function name width, when indent.by.FUN is TRUE', { re = function(n) sprintf('^%s\\S', n_spaces(n)) out1 = capture_usage(barplot.default, width = 60L, indent.by.FUN = TRUE) out2 = capture_usage(stats::lm, width = 60, indent.by.FUN = TRUE) (grepl(re(nchar('barplot(')), out1[-(1L:2L)])) (grepl(re(nchar('lm(')), out2[-1L])) }) assert('usage() breaks line on function name, if function name exceeds width', { reallylongfunctionname = function() {} w = nchar('reallylongfunctionname') out = capture_usage(reallylongfunctionname, w, fail = 'none') (out %==% 'reallylongfunctionname()') warn = tryCatch( usage(reallylongfunctionname, w, fail = 'warn'), warning = function(w) unlist(strsplit(w$message, '\n')) ) l = nchar('reallylongfunctionname()') (warn[2L] %==% sprintf('(%s) \"reallylongfunctionname()\"', l)) reallylongfunctionname = function(bar, baz, ..., a, b, c, d, e) {} res = lapply(c(5L, 10L, 20L), function(w) { list( out = capture_usage(reallylongfunctionname, w, fail = 'none'), warn = tryCatch( usage(reallylongfunctionname, w, fail = 'warn'), warning = function(w) unlist(strsplit(w$message, '\n')) ) ) }) l = nchar('reallylongfunctionname(') (vapply(res, function(.) { all( nchar(.$out[-1L]) <= w, .$warn[2L] %==% sprintf('(%s) \"reallylongfunctionname(\"', l) ) }, logical(1))) }) # Test internal functions (optional) -------------------------------------- exprs = list( quote(foo()), quote(foo(...)), quote(foo(bar)), quote(foo(bar, baz)), quote(foo(bar = 1, baz)), quote(foo(bar = 1, baz = "")), quote(foo(bar, baz = 2)), quote(foo(bar, ..., baz = 2)), quote(foo(bar, ..., baz)) ) counts = list( c(5L), c(4L, 4L), c(4L, 4L), c(4L, 4L, 5L), c(4L, 8L, 5L), c(4L, 8L, 10L), c(4L, 4L, 9L), c(4L, 4L, 5L, 9L), c(4L, 4L, 5L, 5L) ) totals_manual = lapply(counts, cumsum) totals_count_tokens = lapply(exprs, count_tokens) assert('count_tokens() matches manual count of tokens', { (unlist( Map(function(x, y) isTRUE(all.equal(x, y, check.names = FALSE)), totals_count_tokens, totals_manual) )) }) formatR/tests/testit/test-tidy.R0000644000176200001440000001445114053763370016430 0ustar liggesuserslibrary(testit) tidy.res = function(x, ...) { tidy_source(text = x, ..., output = FALSE)$text.tidy } assert('tidy_source() tries to keep R comments', { (tidy.res('1+1#asdf') %==% '1 + 1 #asdf') (tidy.res('paste(1 #asdf\n,2)') %==% 'paste(1 #asdf\n, 2)') (tidy.res(c('# asdf', '1+1')) %==% c('# asdf', '1 + 1')) }) assert('tidy_source() preserves backslashes in comments', { (tidy.res('# \\a \\b \\c') %==% '# \\a \\b \\c') }) assert('tidy_source() can preserve blank lines among non-empty code lines', { (tidy.res(c('if(TRUE){1+1', '', '}', '', '# a comment')) %==% c('if (TRUE) {\n 1 + 1\n\n}', '', '# a comment')) }) x1 = paste(c('#', letters), collapse = ' ') x2 = c('# a b c d e f g h i', '# j k l m n o p q r', '# s t u v w x y z') assert('long comments are wrapped in tidy_source()', { (tidy.res(x1, width.cutoff = 20) %==% one_string(x2)) (tidy.res(rep(x1, 2), width.cutoff = 20) %==% '# a b c d e f g h i # j k l m n o p q r # s t u v w x y z a # b c d e f g h i j # k l m n o p q r s # t u v w x y z' ) (tidy.res(c(x1, '1+1', x1), width.cutoff = 20) %==% c(one_string(x2), '1 + 1', one_string(x2))) }) assert('roxygen comments are not wrapped', { (tidy.res(c(paste("#'", x1), '1*1')) %==% c(paste("#'", x1), '1 * 1')) }) assert('wrap = FALSE does not wrap long comments', { (tidy.res(x1, width.cutoff = 20, wrap = FALSE) %==% x1) }) x1 = ' # only a comment ' x2 = c('', '# only a comment', '', '') assert('tidy_source() can deal with code that only contains a comment', { (tidy.res(x1) %==% c('', '# only a comment', '')) (tidy.res(x2) %==% x2) }) assert('tidy_source() works for empty comments', { (tidy.res('#') %==% '#') (tidy.res(c('#', 'a+b')) %==% c('#', 'a + b')) }) x1 = '{if (TRUE) { 1 } else 2}' assert('tidy_source() moves else back if it is in a standalone line', { (tidy.res(x1) %==% '{\n if (TRUE) {\n 1\n } else 2\n}') }) x1 = '{x=1 else.x=2 }' assert('should not move any lines starting with `else` back to the previous line', { (tidy.res(x1) %==% '{\n x = 1\n else.x = 2\n}') }) x1 = 'if (TRUE) {# comment 1 }' assert('comments after { are moved down one line', { (tidy.res(x1) %==% 'if (TRUE) {\n # comment\n 1\n}') }) assert('empty code returns empty string', { (tidy.res('') %==% '') (tidy.res(c('', ' ')) %==% c('', ' ')) }) assert('keep.comment=FALSE removes comments', { (tidy.res(c('# a comment', '1+1'), comment = FALSE) %==% '1 + 1') }) assert('when comment=FALSE and everything is comment, tidy_source() returns character(0)', { (tidy.res('# a comment', comment = FALSE) %==% character(0)) }) x1 = '1+1 if(F){ } ' assert('keep.blank.line=FALSE removes blank lines', { (tidy.res(x1) %==% c('1 + 1', '', 'if (F) {\n\n}', '')) (tidy.res(x1, blank = FALSE) %==% c('1 + 1', 'if (F) {\n}')) }) assert('The assignment operator = can be replaced with <- when arrow = TRUE', { (tidy.res('x=1;c(x=1) # abc', arrow = TRUE) %==% c('x <- 1', 'c(x = 1) # abc')) (tidy.res('x=1;c(x=1) # abc', arrow = TRUE, comment=FALSE) %==% c('x <- 1', 'c(x = 1)')) }) assert('since R 3.0.0 comments can be written with double quotes in them', { (tidy.res('1+1# hello "world"') %==% "1 + 1 # hello 'world'") }) x1 = 'x=" # this is not a comment "' assert('since R 3.0.0, # in the beginning of a line does not necessarily mean comments', { (tidy.res(x1) %==% 'x = "\n# this is not a comment\n"') }) assert('the shebang is preserved', { (tidy.res(c('#!/usr/bin/Rscript', '1+1')) %==% c('#!/usr/bin/Rscript', '1 + 1')) }) x1 = paste0('x="', r <- rand_string(2000), '"') assert('Long strings (> 1000 chars) can be preserved', { (tidy.res(x1) %==% paste0('x = "', r, '"')) }) x1 = 'x = " this is a character string "' assert('line breaks in strings are preserved instead of being replaced by \\n', { (tidy.res(x1) %==% x1) }) # tests for magrittr newlines x1 = ' iris %>% group_by(Species) %>% summarize(meanlen = mean(Sepal.Length)) %$% arrange(meanlen) %>%meanlen ' x2 = ' iris %>% group_by(Species) %>% summarize(meanlen = mean(Sepal.Length)) %$% arrange(meanlen) %>% meanlen ' assert('magrittr lines are wrapped after the pipes', { (one_string(tidy.res(x1, indent = 2)) %==% x2) }) if (getRversion() >= '4.1.0') assert('The new pipe |> is supported', { (tidy.res('1|>c()') %==% '1 |>\n c()') }) assert('The right arrow -> assignment operator is supported', { (tidy.res('1->a# right assign') %==% '1 -> a # right assign') }) assert('args.newline = TRUE can start function arguments on a new line', { x1 = 'c(aaaaa=1,bbbbb=2,ccccc=3,ddddd=4)' (tidy.res(x1, args.newline = TRUE, width.cutoff = 20) %==% 'c(\n aaaaa = 1, bbbbb = 2,\n ccccc = 3, ddddd = 4\n)') (tidy.res(x1, args.newline = TRUE, width.cutoff = 40) %==% 'c(\n aaaaa = 1, bbbbb = 2, ccccc = 3, ddddd = 4\n)') # strict width (tidy.res(x1, args.newline = TRUE, width.cutoff = I(40)) %==% 'c(\n aaaaa = 1, bbbbb = 2, ccccc = 3,\n ddddd = 4\n)') (tidy.res(x1, args.newline = TRUE, width.cutoff = I(23), indent = 2) %==% 'c(\n aaaaa = 1, bbbbb = 2,\n ccccc = 3, ddddd = 4\n)') # when arguments can fit one line, don't break the line after function name (tidy.res(x1, args.newline = TRUE, width.cutoff = 45) %==% 'c(aaaaa = 1, bbbbb = 2, ccccc = 3, ddddd = 4)') # nested calls x2 = 'lm(y~x1+x2+x3+x4+x5+x6+x7+x8, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)))' (tidy.res(x2, args.newline = TRUE, width.cutoff = 20, indent = 2) %==% 'lm( y ~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8, data = data.frame( y = rnorm(100), x1 = rnorm(100), x2 = rnorm(100) ) )') (tidy.res(x2, args.newline = TRUE, width.cutoff = I(25), indent = 2) %==% 'lm( y ~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8, data = data.frame( y = rnorm(100), x1 = rnorm(100), x2 = rnorm(100) ) )') # also works on function() definitions in addition to function calls x3 = 'my_sum=function(a=1,b=2,c=3,d=4,e=5,f=6,g=7){return(a+b+c)}' (tidy.res(x3, args.newline = TRUE, width.cutoff = 20, indent = 2) %==% 'my_sum = function(\n a = 1, b = 2, c = 3,\n d = 4, e = 5, f = 6,\n g = 7\n) {\n return(a + b + c)\n}') (tidy.res(x3, args.newline = TRUE, width.cutoff = I(33), indent = 2) %==% 'my_sum = function(\n a = 1, b = 2, c = 3, d = 4,\n e = 5, f = 6, g = 7\n) {\n return(a + b + c)\n}') }) formatR/vignettes/0000755000176200001440000000000014055436356013707 5ustar liggesusersformatR/vignettes/formatR.Rmd0000644000176200001440000002554214053624673015774 0ustar liggesusers--- title: formatR subtitle: Format R code automatically author: Yihui Xie date: "`r Sys.Date()`" show_toc: true slug: formatr githubEditURL: https://github.com/yihui/formatR/edit/master/vignettes/formatR.Rmd output: knitr:::html_vignette: toc: yes vignette: > %\VignetteEngine{knitr::rmarkdown} %\VignetteIndexEntry{An Introduction to formatR} --- ```{js, echo=FALSE} // redirect from CRAN to my personal website if (location.protocol === 'https:' && location.href.match('yihui.org') === null) location.href = 'https://yihui.org/formatr/'; ``` ```{r setup, include=FALSE} options(formatR.indent = 4, width = 70) knitr::opts_chunk$set(tidy = TRUE) ``` # 1. Installation You can install **formatR** from [CRAN](https://cran.r-project.org/package=formatR), or yihui.r-universe.dev if you want to test the latest development version: ```{r eval=FALSE} install.packages('formatR', repos = 'http://cran.rstudio.com') # or development version options(repos = c( yihui = 'https://yihui.r-universe.dev', CRAN = 'https://cloud.r-project.org' )) install.packages('formatR') ``` Or check out the [Github repository](https://github.com/yihui/formatR) and install from source if you know what this means. This page is always based on the development version. ```{r} library(formatR) sessionInfo() ``` # 2. Reformat R code The **formatR** package was designed to reformat R code to improve readability; the main workhorse is the function `tidy_source()`. Features include: - Long lines of code and comments are reorganized into appropriately shorter ones; - Spaces and indentation are added where necessary; - Comments are preserved in most cases; - The number of spaces to indent the code (i.e., tab width) can be specified (default is 4); - An `else` statement on a separate line without the leading `}` will be moved one line back; - `=` as an assignment operator can be replaced with `<-`; - The left brace `{` can be moved to a new line; - Arguments of a function call can start on a new line after the function name when they cannot fit on one line; - Lines can be wrapped after pipes (both magrittr pipes such as `%>%` and R's native pipe `|>` are supported). Below is an example of what `tidy_source()` can do. The source code is: ```{r example, eval=FALSE, tidy=FALSE} ## comments are retained; # a comment block will be reflowed if it contains long comments; #' roxygen comments will not be wrapped in any case 1+1 if(TRUE){ x=1 # inline comments }else{ x=2;print('Oh no... ask the right bracket to go away!')} 1*3 # one space before this comment will become two! 2+2+2 # only 'single quotes' are allowed in comments lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line ## here is a long long long long long long long long long long long long long comment that may be wrapped ``` We can copy the above code to clipboard, and type `tidy_source(width.cutoff = 50)` to get: ```{r example, eval=FALSE, tidy.opts=list(width.cutoff=50)} ``` Two applications of `tidy_source()`: - `tidy_dir()` can reformat all R scripts under a directory - `usage()` can reformat the usage of a function, e.g. compare `usage()` with the default output of `args()`: ```{r collapse=TRUE} library(formatR) usage(glm, width = 40) # can set arbitrary width here args(glm) ``` # 3. The Graphical User Interface If the **shiny** packages has been installed, the function `tidy_app()` can launch a Shiny app to reformat R code like this ([live demo](https://yihui.shinyapps.io/formatR/)): ``` {.r} formatR::tidy_app() ``` ```{r echo=FALSE, results='asis'} if (ignore_img <- !is.na(Sys.getenv('_R_CHECK_PACKAGE_NAME_', NA))) cat('') ``` # 4. Evaluate the code and mask output in comments It is often a pain when trying to copy R code from other people's code which has been run in R and the prompt characters (usually `>`) are attached in the beginning of code, because we have to remove all the prompts `>` and `+` manually before we are able to run the code. However, it will be convenient for the reader to understand the code if the output of the code can be attached. This motivates the function `tidy_eval()`, which uses `tidy_source()` to reformat the source code, evaluates the code in chunks, and attaches the output of each chunk as comments which will not actually break the original source code. Here is an example: ```{r comment=NA} set.seed(123) tidy_eval(text = c("a<-1+1;a # print the value", "matrix(rnorm(10),5)")) ``` The default source of the code is from clipboard like `tidy_source()`, so we can copy our code to clipboard, and simply run this in R: ```{r eval=FALSE} library(formatR) tidy_eval() # without specifying any arguments, it reads code from clipboard ``` # 5. Showcase We continue the example code in Section 2, using different arguments in `tidy_source()` such as `arrow`, `blank`, `indent`, `brace.newline` and `comment`, etc. ## Replace `=` with `<-` ```{r example, eval=FALSE, echo=5, tidy.opts=list(arrow=TRUE)} ``` ## Discard blank lines Note the 5th line (an empty line) was discarded: ```{r example, eval=FALSE, echo=1:5, tidy.opts=list(blank = FALSE)} ``` ## Reindent code (2 spaces instead of 4) ```{r example, eval=FALSE, echo=5, tidy.opts=list(indent = 2)} ``` ## Start function arguments on a new line With `args.newline = TRUE`, the example code below ```{r, args-code, eval=FALSE} shiny::updateSelectizeInput(session, "foo", label = "New Label", selected = c("A", "B"), choices = LETTERS, server = TRUE) ``` will be reformatted to: ```{r, args-code, eval=FALSE, tidy.opts=list(args.newline=TRUE)} ``` ## The pipe operators `%>%` and `|>` Since **formatR** 1.9, code lines contains operators `|>`, `%>%`, `%T%`, `%$%`, and/or `%<>%` will be automatically wrapped after these operators. For example, ```{r, pipe-code, eval=FALSE, tidy=FALSE} mtcars %>% subset(am == 0) %>% lm(mpg~hp, data=.) ``` will be reformatted to: ```{r, pipe-code, eval=FALSE, tidy=TRUE} ``` ## Move left braces `{` to new lines ```{r example, eval=FALSE, echo=5, tidy.opts=list(brace.newline = TRUE)} ``` ## Do not wrap comments ```{r example, eval=FALSE, echo=11:12, tidy.opts=list(wrap = FALSE)} ``` ## Discard comments ```{r example, eval=FALSE, tidy.opts=list(comment = FALSE, width.cutoff = 50)} ``` # 6. Further notes The tricks used in this packages are very dirty. There might be dangers in using the functions in **formatR**. Please read the next section carefully to know exactly how comments are preserved. The best strategy to avoid failure is to put comments in complete lines or after *complete* R expressions. Below are some known cases in which `tidy_source()` fails. ## Inline comments after an incomplete expression or ; ``` {.r} 1 + 2 + ## comments after an incomplete line 3 + 4 x <- ## this is not a complete expression 5 x <- 1; # you should not use ; here! ``` Code with comments after incomplete R expression cannot be reformatted by **formatR**. By the way, `tidy_source()` will move comments after `{` to the next line, e.g., ```{r comment-brace, tidy=FALSE, eval=FALSE} if (TRUE) {## comments } ``` will become ```{r comment-brace, eval=FALSE} ``` ## Inappropriate blank lines Blank lines are often used to separate complete chunks of R code, and arbitrary blank lines may cause failures in `tidy_source()` as well when the argument `blank = TRUE`, e.g. ``` {.r} if (TRUE) {'this is a BAD style of R programming!'} else 'failure!' ``` There should not be a blank line after the `if` statement. Of course `blank = FALSE` will not fail in this case. ## `?` with comments We can use the question mark (`?`) to view the help page, but **formatR** package is unable to correctly format the code using `?` with comments, e.g. ``` {.r} ?sd # help on sd() ``` In this case, it is recommended to use the function `help()` instead of the short-hand version `?`. # 7. How does `tidy_source()` actually work? In a nutshell, `tidy_source(text = code)` is basically `deparse(parse(text = code))`, but actually it is more complicated only because of one thing: `deparse()` drops comments, e.g., ```{r} deparse(parse(text = '1+2-3*4/5 # a comment')) ``` The method to preserve comments is to protect them as strings in R expressions. For example, there is a single line of comments in the source code: ``` {.r} # asdf ``` It will be first masked as ``` {.r} invisible(".IDENTIFIER1 # asdf.IDENTIFIER2") ``` which is a legal R expression, so `base::parse()` can deal with it and will no longer remove the disguised comments. In the end the identifiers will be removed to restore the original comments, i.e. the strings `invisible(".IDENTIFIER1` and `.IDENTIFIER2")` are replaced with empty strings. Inline comments are handled differently: two spaces will be added before the hash symbol `#`, e.g. ``` {.r} 1+1# comments ``` will become ``` {.r} 1+1 # comments ``` Inline comments are first disguised as a weird operation with its preceding R code, which is essentially meaningless but syntactically correct! For example, ``` {.r} 1+1 %\b% "# comments" ``` then `base::parse()` will deal with this expression; again, the disguised comments will not be removed. In the end, inline comments will be freed as well (remove the operator `%\b%` and surrounding double quotes). All these special treatments to comments are due to the fact that `base::parse()` and `base::deparse()` can tidy the R code at the price of dropping all the comments. # 8. Global options There are global options which can override some arguments in `tidy_source()`: | argument | global option | default | |-----------------|------------------------------------|--------------------| | `comment` | `options('formatR.comment')` | `TRUE` | | `blank` | `options('formatR.blank')` | `TRUE` | | `arrow` | `options('formatR.arrow')` | `FALSE` | | `indent` | `options('formatR.indent')` | `4` | | `wrap` | `options('formatR.wrap')` | `TRUE` | | `width.cutoff` | `options('formatR.width')` | `options('width')` | | `brace.newline` | `options('formatR.brace.newline')` | `FALSE` | | `args.newline` | `options('formatR.args.newline')` | `FALSE` | Also note that single lines of long comments will be wrapped into shorter ones automatically when `wrap = TRUE`, but roxygen comments will not be wrapped (i.e., comments that begin with `#'`). formatR/R/0000755000176200001440000000000014053512231012061 5ustar liggesusersformatR/R/utils.R0000644000176200001440000002301214053767573013367 0ustar liggesusers# replace `=` by `<-` in expressions replace_assignment = function(exp) { wc = codetools::makeCodeWalker( call = function(e, w) { cl = codetools::walkCode(e[[1]], w) arg = lapply(as.list(e[-1]), function(a) if (missing(a)) NA else { codetools::walkCode(a, w) }) as.call(c(list(cl), arg)) }, leaf = function(e, w) { if (length(e) == 0 || inherits(e, "srcref")) return(NULL) # x = 1 is actually `=`(x, 1), i.e. `=` is a function if (identical(e, as.name("="))) e <- as.name("<-") e }) lapply(as.list(exp), codetools::walkCode, w = wc) } parse_data = function(x) { d = utils::getParseData(parse_source(x)) d[d$terminal, ] } ## mask comments to cheat R mask_comments = function(x, keep.blank.line, wrap, arrow, args.newline, spaces) { d = parse_data(x) if ((n <- nrow(d)) == 0) return(x) d = fix_parse_data(d, x) if (args.newline) d = insert_arg_breaks(d, spaces) d.line = d$line1; d.line2 = d$line2; d.token = d$token; d.text = d$text # move else back for (i in which(d.token == 'ELSE')) { delta = d.line[i] - d.line[i - 1] d.line[i:n] = d.line[i:n] - delta d.line2[i:n] = d.line2[i:n] - delta } # how many blank lines after each token? blank = c(pmax(d.line[-1] - d.line2[-n] - 1, 0), 0) # replace = with <- when = means assignment if (arrow) d.text[d.token == 'EQ_ASSIGN'] = '<-' i = d.token == 'COMMENT' # double backslashes and replace " with ' in comments d.text[i] = gsub('"', "'", gsub('\\\\', '\\\\\\\\', d.text[i])) c0 = d.line[-1] != d.line[-n] # is there a line change? c1 = i & c(TRUE, c0 | (d.token[-n] == "'{'")) # must be comment blocks c2 = i & !c1 # inline comments c3 = c1 & grepl("^#+[-'+]", d.text) # roxygen or knitr spin() comments if (wrap) { if (grepl('^#!', d.text[1])) c3[1] = TRUE # shebang comment } else c3 = c1 # comments not to be wrapped # collapse blocks of comments i1 = which(c1 & !c3) # do not wrap roxygen comments j1 = i1[1] if (length(i1) > 1) for (i in 2:length(i1)) { # two neighbor lines of comments if (d.line[i1[i]] - d.line[i1[i - 1]] == 1) { j2 = i1[i] d.text[j1] = paste(d.text[j1], sub('^#+', '', d.text[j2])) d.text[j2] = '' c1[j2] = FALSE # the second line is no longer a comment } else j1 = i1[i] } # mask block and inline comments d.text[c1] = sprintf('invisible("%s%s%s")', begin.comment, d.text[c1], end.comment) d.text[c2] = sprintf('%%\b%% "%s"', d.text[c2]) # add blank lines if (keep.blank.line) for (i in seq_along(d.text)) { if (blank[i] > 0) d.text[i] = one_string(c(d.text[i], rep(blank.comment, blank[i]))) } # break lines after some infix operators such as %>% d.text = gsub(paste0('^(%)(', infix_ops, ')(%)$'), paste0('\\1\b\\2', spaces, '\\3'), d.text) # similarly break lines after |>; later restore %\b|>% to |> in unmask_source() d.text[d.text == '|>'] = paste0('%', '\b|>', spaces, '%') # preserve the assignment operator -> d.text[d.text == '->'] = '%\b->%' unlist(lapply(split(d.text, d.line), paste, collapse = ' '), use.names = FALSE) } infix_ops = '[>$]|T>|<>' restore_infix = function(x) { x = gsub('%\b([^ %]+)%', '\\1', x) x = gsub('%\b([|]>) +%\\s*', '\\1', x) x = gsub('(%)\b([^ ]+) +(%)\\s*$', '\\1\\2\\3', x) x } # no blank lines before an 'else' statement! move_else = function(x) { blank = grepl('^\\s*$', x) if (!any(blank)) return(x) else.line = grep('^\\s*else(\\s+|$)', x) for (i in else.line) { j = i - 1 while (blank[j]) { blank[j] = FALSE; j = j - 1 # search backwards & rm blank lines warning('removed blank line ', j, " (should not put an 'else' in a separate line!)") } } x[blank] = blank.comment x } # reflow comments (excluding roxygen comments) reflow_comments = function(x, width, wrap) { c1 = grepl(mat.comment, x) x = gsub(pat.comment, '', x) # strip the invisible() masks if (!wrap) return(x) x[c1] = restore_bs(x[c1]) c2 = c1 & !grepl("^\\s*#+[-'+!]", x) # extract indent & comment prefix, e.g., '## ' r = '^(\\s*#+)\\s*(.*)' x[c2] = unlist(lapply(x[c2], function(z) { p = sub(r, '\\1', z) z = sub(r, '\\2', z) z = one_string(strwrap(z, width, prefix = paste0(p, ' '))) # the comment might be empty, in which case we return the comment chars if (z == '') p else z })) x } # reindent lines with a different number of spaces reindent_lines = function(text, spaces = rep_chars(n), n = 2) { if (length(text) == 0) return(text) if (spaces == ' ') return(text) # no need to do anything t1 = gsub('^( *)(.*)', '\\1', text) t2 = gsub('^( *)(.*)', '\\2', text) paste0(gsub(' {4}', spaces, t1), t2) } # move { to the next line move_leftbrace = function(x) { if (length(idx <- grep('(\\)|else) \\{$', x)) == 0) return(x) # indent the same amount of spaces as the { lines b = gsub('^( *)(.*)', '\\1{', x[idx]) j = 0 x[idx] = gsub(' \\{$', '', x[idx]) for (i in seq_along(idx)) { x = append(x, b[i], idx[i] + j) j = j + 1 } x } # parse but do not keep source (moved from knitr) parse_only = function(code) { if (length(code) == 0) return(expression()) base::parse(text = code, keep.source = FALSE) } parse_source = function(lines) { parse(text = lines, keep.source = TRUE) } # restore backslashes restore_bs = function(x) gsub('\\\\\\\\', '\\\\', x) # a workaround for the R bug (long strings are truncated in getParseData()): # https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=16354 fix_parse_data = function(d, x) { if (length(s <- which(d$token == 'STR_CONST')) == 0) return(d) ws = s[grep('^\\[\\d+ (wide )?chars quoted with \'"\'\\]$', d$text[s])] for (i in ws) { di = d[i, , drop = FALSE] d[i, 'text'] = get_src_string(x, di$line1, di$line2, di$col1, di$col2) } d[s, 'text'] = mask_line_break(d[s, 'text']) d } get_src_string = function(x, l1, l2, c1, c2) { if (l1 == l2) return(substr(x[l1], c1, c2)) x[l1] = substr(x[l1], c1, nchar(x[l1])) x[l2] = substr(x[l2], 1, c2) one_string(x[l1:l2]) } # generate a random string CHARS = c(letters, LETTERS, 0:9) rand_string = function(len = 32) { paste(sample(CHARS, len, replace = TRUE), collapse = '') } .env = new.env() .env$line_break = NULL # protect \n in source code, otherwise deparse() will change it to \\n mask_line_break = function(x) { if (length(grep('\n', x)) == 0) return(x) m = (function() { for (i in 2:10) { for (j in 1:100) if (length(grep(s <- rand_string(i), x)) == 0) return(s) } })() if (is.null(m)) return(x) .env$line_break = m gsub('\n', m, x) } # add a long argument to a function call, so that other arguments can be pushed # to the next line; this is for breaking arguments onto new lines, e.g., # c(a = 1) -> c(`\b \b`, a = 1, `\b \b#`) -> # c(`\b \b`, # a = 1, # `\b \b#`) # -> # c( # a = 1 # ) insert_arg_breaks = function(d, spaces) { if (length(i <- which(d$token %in% c('SYMBOL_FUNCTION_CALL', 'FUNCTION'))) == 0) return(d) i1 = i[d[i + 1, 'token'] == "'('"] + 1 # the next line must be ( i1 = i1[d[i1 + 1, 'token'] != "')'"] # there must be arguments inside () if (length(i1) == 0) return(d) i2 = which(d$token == "')'") i2 = i2[d[i2, 'parent'] %in% d[i1, 'parent']] # ) that shares same parent as ( if (length(i1) != length(i2)) { warning('( and ) do not match in function calls.') return(d) } s1 = arg_spaces(spaces, d[i1, 'parent']) s2 = arg_spaces(spaces, d[i2, 'parent'], '#') s3 = gsub('#', '##', s2) d[i1, 'text'] = paste0('(', s1, ',') d[i2, 'text'] = paste0(',', s2, ',', s3, ')') d } arg_spaces = function(x, id, id2 = '') sprintf('`%s\b%s\b%s`', id, x, id2) # restore breaks for all function calls restore_arg_breaks = function( x, width, spaces = rep_chars(width), indent = ' ', split = FALSE ) { s = gsub('\b', '\\\\\\\\b', arg_spaces(spaces, '([0-9]+)', '#{0,2}')) if (length(grep(s, x)) == 0) return(x) if (split) x = one_string(x) m = gregexpr(s, x) id = gsub(s, '\\1', unlist(regmatches(x, m))) for (i in sort(as.integer(unique(id)))) { x = restore_arg_break(x, i, s, width, indent) } if (split) x = split_lines(x) x } # restore one function call restore_arg_break = function(x, i, s, width, indent) { s2 = paste0(s, '(,\\s*)?') t = sub('([0-9]+)', i, s, fixed = TRUE) r = sprintf('(\\()%s,\\s*?(\n\\s*)(.*?)\\s*,\\s*%s,\\s*?(\n\\s*)%s(\\))', t, t, t) m = gregexpr(r, x) regmatches(x, m) = lapply(regmatches(x, m), function(z) { if (length(z) == 0) return(z) # first try not to move arguments onto new lines and check if the code # exceeds the desired width x1 = gsub(r, '\\1\\3\\5', z) # if all arguments fit one line, just put them on one line if (!grepl('\n', x1) && !any(exceed_width(split_lines(gsub(s2, '', x1)), width))) return(x1) x1 = gsub(r, '\\1\\2\\3\\4\\5', z) # indent ) back one level sub(sprintf('(\n)%s(\\s*\\))$', indent), '\\1\\2', x1) }) x } exceed_width = function(x, width) nchar(x, type = 'width') > width split_lines = function(x) unlist(strsplit(x, '\n')) rep_chars = function(width, char = ' ') paste(rep(char, width), collapse = '') trimws = function(x, which = c('both', 'left', 'right')) { switch(match.arg(which), both = gsub('^\\s+|\\s+$', '', x), left = gsub('^\\s+', '', x), right = gsub('\\s+$', '', x) ) } one_string = function(..., collapse = '\n') paste(..., collapse = collapse) restore_encoding = function(x, enc) { if (length(enc) != 1) return(x) xenc = special_encoding(x) iconv(x, if (length(xenc) == 0) '' else xenc, enc) } special_encoding = function(x) setdiff(Encoding(x), 'unknown') formatR/R/tidy.R0000644000176200001440000002564614053606477013212 0ustar liggesusers#' Reformat R code while preserving blank lines and comments #' #' Read R code from a file or the clipboard and reformat it. That this function #' preserves blank lines and comments is a different behavior from that of #' \code{\link{parse}()} and \code{\link{deparse}()}. This function can also #' replace \code{=} with \code{<-} where \code{=} means assignment, and #' re-indent code with a specified number of spaces. #' #' Value of the argument \code{width.cutoff} wrapped in \code{\link{I}()} (e.g., #' \code{I(60)}) will be treated as the \emph{upper bound} of the line width. #' The corresponding argument to \code{deparse()} is a lower bound, so the #' function will perform a binary search for a width value that can make #' \code{deparse()} return code with line width smaller than or equal to the #' \code{width.cutoff} value. If the search fails, a warning will signal, #' suppressible by global option \code{options(formatR.width.warning = FALSE)}. #' @param source A character string: file path to the source code (defaults to #' the clipboard). #' @param comment Whether to keep comments. #' @param blank Whether to keep blank lines. #' @param arrow Whether to replace the assign operator \code{=} with \code{<-}. #' @param brace.newline Whether to put the left brace \code{\{} to a new line. #' @param indent Number of spaces to indent the code. #' @param wrap Whether to wrap comments to the linewidth determined by #' \code{width.cutoff} (roxygen comments will never be wrapped). #' @param width.cutoff An integer in \code{[20, 500]}: if a line's character #' length is at or over this number, the function will try to break it into a #' new line. In other words, this is the \emph{lower bound} of the line width. #' See \sQuote{Details} if an upper bound is desired instead. #' @param args.newline Whether to start the arguments of a function call on a #' new line instead of after the function name and \code{(} when the arguments #' cannot fit one line. #' @param output Whether to output to the console or a file using #' \code{\link{cat}()}. #' @param text An alternative way to specify the input: if \code{NULL}, the #' function will use the \code{source} argument; if a character vector #' containing the source code, the function will use this and ignore the #' \code{source} argument. #' @param ... Other arguments passed to \code{\link{cat}()}, e.g. \code{file} #' (this can be useful for batch-processing R scripts, e.g. #' \code{tidy_source(source = 'input.R', file = 'output.R')}). #' @return A list with components \item{text.tidy}{the reformatted code as a #' character vector} \item{text.mask}{the code containing comments, which are #' masked in assignments or with the weird operator}. #' @note Be sure to read the reference to know other limitations. #' @author Yihui Xie <\url{https://yihui.org}> with substantial contribution #' from Yixuan Qiu <\url{https://yixuan.blog}> #' @seealso \code{\link{parse}()}, \code{\link{deparse}()} #' @references \url{https://yihui.org/formatR/} (an introduction to this #' package, with examples and further notes) #' @import stats utils #' @export #' @example inst/examples/tidy.source.R tidy_source = function( source = 'clipboard', comment = getOption('formatR.comment', TRUE), blank = getOption('formatR.blank', TRUE), arrow = getOption('formatR.arrow', FALSE), brace.newline = getOption('formatR.brace.newline', FALSE), indent = getOption('formatR.indent', 4), wrap = getOption('formatR.wrap', TRUE), width.cutoff = getOption('formatR.width', getOption('width')), args.newline = getOption('formatR.args.newline', FALSE), output = TRUE, text = NULL, ... ) { if (is.null(text)) { if (source == 'clipboard' && Sys.info()['sysname'] == 'Darwin') { source = pipe('pbpaste'); on.exit(close(source), add = TRUE) # use readChar() instead of readLines() in case users didn't copy the last # \n into clipboard, e.g., https://github.com/yihui/formatR/issues/54 text = readChar(source, getOption('formatR.clipboard.size', 1e5)) text = unlist(strsplit(text, '\n')) } else { text = readLines(source, warn = FALSE) } } enc = special_encoding(text) if (length(text) == 0L || all(grepl('^\\s*$', text))) { if (output) cat('\n', ...) return(list(text.tidy = text, text.mask = text)) } if (blank) { one = one_string(text) # record how many line breaks before/after n1 = attr(regexpr('^\n*', one), 'match.length') n2 = attr(regexpr('\n*$', one), 'match.length') } on.exit(.env$line_break <- NULL, add = TRUE) if (width.cutoff > 500) width.cutoff[1] = 500 if (width.cutoff < 20) width.cutoff[1] = 20 # insert enough spaces into infix operators such as %>% so the lines can be # broken after the operators spaces = rep_chars(width.cutoff) if (comment) text = mask_comments(text, blank, wrap, arrow, args.newline, spaces) text.mask = tidy_block( text, width.cutoff, arrow && !comment, rep_chars(indent), brace.newline, wrap, args.newline, spaces ) text.tidy = if (comment) unmask_source(text.mask) else text.mask # restore new lines in the beginning and end if (blank) text.tidy = c(rep('', n1), text.tidy, rep('', n2)) if (output) cat(text.tidy, sep = '\n', ...) invisible(list( text.tidy = restore_encoding(text.tidy, enc), text.mask = restore_encoding(text.mask, enc) )) } # some tokens that should be rare to appear in code from real world, mainly to # protect comments and blank lines begin.comment = '.BeGiN_TiDy_IdEnTiFiEr_HaHaHa' end.comment = '.HaHaHa_EnD_TiDy_IdEnTiFiEr' pat.comment = sprintf('invisible\\("\\%s|\\%s"\\)', begin.comment, end.comment) mat.comment = sprintf('invisible\\("\\%s([^"]*)\\%s"\\)', begin.comment, end.comment) inline.comment = ' %\b%[ ]*"([ ]*#[^"]*)"' blank.comment = sprintf('invisible("%s%s")', begin.comment, end.comment) blank.comment2 = paste0('^\\s*', gsub('\\(', '\\\\(', blank.comment), '\\s*$') # first, perform a (semi-)binary search to find the greatest cutoff width such # that the width of the longest line <= `width`; if the search fails, use # brute-force to try all possible widths deparse2 = function( expr, width, spaces = '', indent = ' ', warn = getOption('formatR.width.warning', TRUE) ) { wmin = 20 # if deparse() can't manage it with width.cutoff <= 20, issue a warning wmax = min(500, width + 10) # +10 because a larger width may result in smaller actual width r = seq(wmin, wmax) k = setNames(rep(NA, length(r)), as.character(r)) # results of width checks d = p = list() # deparsed results and lines exceeding desired width # pattern for pipe operators like %>% pat.infix = paste0('(%)(', infix_ops, ') {', width, '}(%)$') check_width = function(w) { i = as.character(w) if (!is.na(x <- k[i])) return(x) x = deparse(expr, w) x = trimws(x, 'right') d[[i]] <<- x x2 = grep(pat.comment, x, invert = TRUE, value = TRUE) # don't check comments x2 = gsub(pat.infix, '\\1\\2\\3', x2) # remove extra spaces in %>% operators x2 = restore_infix(x2) x2 = reindent_lines(x2, indent) x2 = restore_arg_breaks(x2, width, spaces, indent, split = TRUE) p[[i]] <<- x2[exceed_width(x2, width)] k[i] <<- length(p[[i]]) == 0 } # if the desired width happens to just work, return the result if (check_width(w <- width)) return(d[[as.character(w)]]) repeat { if (!any(is.na(k))) break # has tried all possibilities if (wmin >= wmax) break w = ceiling((wmin + wmax)/2) if (check_width(w)) wmin = w else wmax = wmax - 2 } # try all the rest of widths if no suitable width has been found if (!any(k, na.rm = TRUE)) for (i in r[is.na(k)]) check_width(i) r = r[which(k)] if ((n <- length(r)) > 0) return(d[[as.character(r[n])]]) i = as.character(width) if (warn) warning( 'Unable to find a suitable cut-off to make the line widths smaller than ', width, ' for the line(s) of code:\n', one_string(' ', p[[i]]), call. = FALSE ) d[[i]] } # wrapper around parse() and deparse() tidy_block = function( text, width = getOption('width'), arrow = FALSE, indent = ' ', brace.newline = FALSE, wrap = TRUE, args.newline = FALSE, spaces = rep_chars(width) ) { exprs = parse_only(text) if (length(exprs) == 0) return(character(0)) exprs = if (arrow) replace_assignment(exprs) else as.list(exprs) deparse = if (inherits(width, 'AsIs')) { function(x, width) deparse2(x, width, spaces, indent) } else base::deparse unlist(lapply(exprs, function(e) { x = deparse(e, width) x = trimws(x, 'right') x = reindent_lines(x, indent) # remove white spaces on blank lines x = gsub(blank.comment2, '', x) x = reflow_comments(x, width, wrap) if (brace.newline) x = move_leftbrace(x) x = restore_infix(x) x = one_string(x) if (args.newline) x = restore_arg_breaks(x, width, spaces, indent) x })) } # Restore the real source code from the masked text unmask_source = function(x) { if (length(x) == 0) return(x) m = .env$line_break if (!is.null(m)) x = gsub(m, '\n', x) # if the comments were separated into the next line, then remove '\n' after # the identifier first to move the comments back to the same line x = gsub('(%\b%)[ ]*\n', '\\1', x) # move 'else ...' back to the last line x = gsub('\n\\s*else(\\s+|$)', ' else\\1', x) if (any(grepl('\\\\\\\\', x)) || any(grepl(inline.comment, x))) { m = gregexpr(inline.comment, x) regmatches(x, m) = lapply(regmatches(x, m), restore_bs) } # inline comments should be terminated by $ or \n x = gsub(paste(inline.comment, '(\n|$)', sep = ''), ' \\1\\2', x) # the rest of inline comments should be appended by \n gsub(inline.comment, ' \\1\n', x) } #' Format all R scripts under a directory, or specified R scripts #' #' Look for all R scripts under a directory (using the pattern #' \code{"[.][RrSsQq]$"}), then tidy them with \code{\link{tidy_source}()}. If #' successful, the original scripts will be overwritten with reformatted ones. #' Please back up the original directory first if you do not fully understand #' the tricks used by \code{\link{tidy_source}()}. \code{tidy_file()} formats #' scripts specified by file names. #' @param path The path to a directory containning R scripts. #' @param recursive Whether to recursively look for R scripts under \code{path}. #' @param ... Other arguments to be passed to \code{\link{tidy_source}()}. #' @param file A vector of filenames. #' @return Invisible \code{NULL}. #' @author Yihui Xie (\code{tidy_dir}) and Ed Lee (\code{tidy_file}) #' @seealso \code{\link{tidy_source}()} #' @export #' @examples #' library(formatR) #' #' path = tempdir() #' file.copy(system.file('demo', package = 'base'), path, recursive=TRUE) #' tidy_dir(path, recursive=TRUE) tidy_dir = function(path = '.', recursive = FALSE, ...) { tidy_file(list.files( path, pattern = '[.][RrSsQq]$', full.names = TRUE, recursive = recursive ), ...) } #' @export #' @rdname tidy_dir tidy_file = function(file, ...) { for (f in file) { message("tidying ", f) try(tidy_source(f, file = f, ...)) } } formatR/R/usage.R0000644000176200001440000001460414053066720013324 0ustar liggesusers# the code below was mostly contributed by @egnha from # https://github.com/yihui/formatR/pull/66 deparse_collapse = function(x) { d = deparse(x) if (length(d) > 1L) { paste(trimws(d, which = 'both'), collapse = ' ') } else { d } } count_tokens = function(.call) { if (length(.call) == 1L) { # +2 for '()' return(nchar(.call) + 2L) } # +1 for value-delimiting '(', ',', or ')' cnt_val = nchar(vapply(.call, deparse_collapse, character(1L))) + 1L nms = names(.call[-1L]) if (is.null(nms)) nms = character(length(.call[-1L])) # nchar() of argument names cnt_nm = nchar(nms) # +3 for ' = ', for argument-value pairs cnt_nm[cnt_nm != 0L] = cnt_nm[cnt_nm != 0L] + 3L # +1 for space before name, beyond the first argument cnt_nm[-1L] = cnt_nm[-1L] + 1L # function itself is not a named component cnt_nm = c(0L, cnt_nm) cumsum(cnt_val + cnt_nm) } # counts is a strictly increasing, positive integer vector find_breaks = function(counts, width, indent, track, counted = 0L) { if (!length(counts)) { return(list(breaks = NULL, overflow = NULL)) } overflow = NULL shift = if (counted == 0L) 0L else indent fits = counts - counted + shift <= width i = which.min(fits) - 1L if (i == 0L) { if (fits[[1L]]) { # all components of fits_on_line are TRUE i = length(counts) } else { # all components of fits_on_line are FALSE overflow = track(counted, counts[1L], shift) i = 1L } } post_space = if (i == 1L && counted == 0L) 0L else 1L rest = Recall(counts[-(1L:i)], width, indent, track, counts[i] + post_space) list( breaks = c(counts[i], rest$breaks), overflow = c(overflow, rest$overflow) ) } overflow_message = function(overflow, width, indent, text) { header = sprintf('Could not fit all lines to width %s (with indent %s):', width, indent) idxs = seq_along(overflow) args = vapply(idxs[idxs %% 3L == 1L], function(i) { l = paste(c(rep(' ', overflow[i + 2L]), trimws(substr(text, overflow[i] + 1L, overflow[i + 1L]), which = 'left')), collapse = '') sprintf('(%s) \"%s\"', nchar(l), l) }, character(1L)) one_string(c(header, args)) } tidy_usage = function(nm, usg, width, indent, fail) { text = paste(trimws(usg, which = 'both'), collapse = ' ') text = sub(sprintf('^%s\\s*', nm), nm, text) expr = parse(text = text)[[1L]] track_overflow = if (fail == 'none') function(...) NULL else base::c breaks = find_breaks(count_tokens(expr), width, indent, track_overflow) if (length(breaks$overflow)) { signal = switch(fail, stop = 'stop', warn = 'warning') msg = overflow_message(breaks$overflow, width, indent, text) getFromNamespace(signal, 'base')(msg, call. = FALSE) } breaks = c(0L, breaks$breaks) newline = paste(c('\n', character(indent)), collapse = ' ') paste( vapply(1L:(length(breaks) - 1L), function(i) { trimws(substr(text, breaks[i] + 1L, breaks[i + 1L]), which = 'left') }, character(1L)), collapse = newline ) } #' Show the usage of a function #' #' Print the reformatted usage of a function. The arguments of the function are #' searched by \code{\link{argsAnywhere}()}, so the function can be either #' exported or non-exported from a package. S3 methods will be marked. #' @param FUN The function name. #' @param width The width of the output. #' @param tidy Whether to reformat the usage code. #' @param output Whether to print the output to the console (via #' \code{\link{cat}()}). #' @param indent.by.FUN Whether to indent subsequent lines by the width of the #' function name (see \dQuote{Details}). #' @param fail A character string that represents the action taken when the #' width constraint is unfulfillable. "warn" and "stop" will signal warnings #' and errors, while "none" will do nothing. #' @return Reformatted usage code of a function, in character strings #' (invisible). #' @details Line breaks in the output occur between arguments. In particular, #' default values of arguments will not be split across lines. #' #' When \code{indent.by.FUN} is \code{FALSE}, indentation is set by the option #' \code{\link{getOption}("formatR.indent", 4L)}, the default value of the #' \code{indent} argument of \code{\link{tidy_source}()}. #' @seealso \code{\link{tidy_source}()} #' @export #' @examples library(formatR) #' usage(var) #' #' usage(plot) #' #' usage(plot.default) # default method #' usage('plot.lm') # on the 'lm' class #' #' usage(usage) #' #' usage(barplot.default, width = 60) # output lines have 60 characters or less #' #' # indent by width of 'barplot(' #' usage(barplot.default, width = 60, indent.by.FUN = TRUE) #' #' \dontrun{ #' # a warning is raised because the width constraint is unfulfillable #' usage(barplot.default, width = 30) #' } usage = function(FUN, width = getOption('width'), tidy = TRUE, output = TRUE, indent.by.FUN = FALSE, fail = c('warn', 'stop', 'none')) { fail = match.arg(fail) fn = as.character(substitute(FUN)) res = capture.output(if (is.function(FUN)) args(FUN) else { do.call(argsAnywhere, list(fn)) }) if (identical(res, 'NULL')) return() res[1] = substring(res[1], 9) # rm 'function ' in the beginning isS3 = FALSE if (length(fn) == 3 && (fn[1] %in% c('::', ':::'))) fn = fn[3] if (grepl('.', fn, fixed = TRUE)) { n = length(parts <- strsplit(fn, '.', fixed = TRUE)[[1]]) for (i in 2:n) { gen = paste(parts[1L:(i - 1)], collapse = ".") cl = paste(parts[i:n], collapse = ".") if (gen == "" || cl == "") next if (!is.null(f <- getS3method(gen, cl, TRUE)) && !is.null(environment(f))) { res[1] = paste(gen, res[1]) header = if (cl == 'default') '## Default S3 method:' else sprintf("## S3 method for class '%s'", cl) res = c(header, res) isS3 = TRUE break } } } if (!isS3) res[1] = paste(fn, res[1]) if ((n <- length(res)) > 1 && res[n] == 'NULL') res = res[-n] # rm last element 'NULL' if (!tidy) { if (output) cat(res, sep = '\n') return(invisible(res)) } nm = if (isS3) gen else fn usg = if (isS3) res[-1L] else res indent = if (indent.by.FUN) { # +1 for '(' nchar(nm) + 1L } else { # Default indent for tidy_source() getOption('formatR.indent', 4L) } out = tidy_usage(nm, usg, width, indent, fail) if (isS3) out = c(res[1L], out) if (output) cat(out, sep = '\n') invisible(out) } formatR/R/shiny.R0000644000176200001440000000054114052075675013355 0ustar liggesusers#' A Shiny app to format R code #' #' Run a Shiny app that formats R code via \code{\link{tidy_source}()}. This app #' uses input widgets, such as checkboxes, to pass arguments to #' \code{tidy_source()}. #' @export #' @examples if (interactive()) formatR::tidy_app() tidy_app = function() { shiny::runApp(system.file('shiny', package = 'formatR')) } formatR/R/eval.R0000644000176200001440000000304214052075675013151 0ustar liggesusers#' Insert output to source code #' #' Evaluate R code by chunks, then insert the output to each chunk. As the #' output is masked in comments, the source code will not break. #' @param source The input file name (by default the clipboard; see #' \code{\link{tidy_source}()}). #' @param ... Other arguments passed to \code{\link{tidy_source}()}. #' @param file The file name to write to via \code{\link{cat}()}. #' @param prefix The prefix to mask the output. #' @param envir The environment in which to evaluate the code. By default the #' parent frame; set \code{envir = NULL} or \code{envir = new.env()} to avoid #' the possibility of contaminating the parent frame. #' @return Evaluated R code with corresponding output (printed on screen or #' written to a file). #' @export #' @references \url{https://yihui.org/formatR/} #' @examples library(formatR) #' ## evaluate simple code as a character vector #' tidy_eval(text = c('a<-1+1;a','matrix(rnorm(10),5)')) #' #' ## evaluate a file #' tidy_eval(system.file('format', 'messy.R', package = 'formatR')) tidy_eval = function(source = 'clipboard', ..., file = '', prefix = '## ', envir = parent.frame()) { txt = tidy_source(source, ..., output = FALSE)$text.tidy for (i in 1:length(txt)) { cat(txt[i], '\n', sep = '', file = file, append = TRUE) out = capture.output(eval(res <- parse_only(txt[i]), envir = envir)) if (length(res) > 0L && length(out) > 0L) { cat(paste(prefix, out, sep = ''), sep = '\n', file = file, append = TRUE) cat('\n', file = file, append = TRUE) } } } formatR/MD50000644000176200001440000000264514055461351012207 0ustar liggesusers865bb3ee187aa3f5a2d5a10652f97ff3 *DESCRIPTION 2aa3b1fd5a7f0141f3bf464beba80d09 *NAMESPACE 96dfd76e3dc65f34d712be3558ea2418 *R/eval.R 078746b2c4376aa692665ced989e3316 *R/shiny.R ba571041a93667c03aa04b19e91baa8c *R/tidy.R ba2a92307e602635d7b9682ef9a2b621 *R/usage.R 60dcd8fbbbc79020a63ccf6b59dc0fa4 *R/utils.R c72b4976f76406baef83291cfb7e312b *build/vignette.rds 7d6fc696d9e51bd23792eb5ac8b4c595 *inst/NEWS.Rd d0750e11da60cf11204fab4ada8ac373 *inst/doc/formatR.R 691398ac7dda05e3d1f4507b3a4ec61a *inst/doc/formatR.Rmd a754c2587594c9e2648f23756a903edb *inst/doc/formatR.html 4cc1fd6c70617c2386287e60a8839fd9 *inst/format/messy.R 0c07e50e7846ac0e8b3b63d1053a7954 *inst/shiny/DESCRIPTION 70a6ecc0f945539a7af67027fd0e28a5 *inst/shiny/Readme.md 38b6a5f3c26f08692bb5a92e1faab7a1 *inst/shiny/server.R 41d67f25193832b864349c7c357dca42 *inst/shiny/ui.R 52694920c5ee5dae5a0987c7dd441b8c *inst/shiny/www/shiny-handler.js e4ad0dd8b01b7349d874491290266ea5 *man/tidy_app.Rd a728c5f1b5559cb44f4c6f682a1c8fdc *man/tidy_dir.Rd c47c2e7c2db4bfad1320fcce53158a80 *man/tidy_eval.Rd bcabdecd2bc7e2811d3d4914a3a357a6 *man/tidy_source.Rd 0448390abf5b5d86109f746752bcaffc *man/usage.Rd 7bbb218a408b00f834be178333ec0386 *tests/test-all.R 572e49830bf1f88f87ebbca9459454e3 *tests/testit/test-tidy.R b65e5b4e73c3725edb2a551b3be0002b *tests/testit/test-usage.R d913260811ef501d53c4250979b7310d *tests/testit/test-utils.R 691398ac7dda05e3d1f4507b3a4ec61a *vignettes/formatR.Rmd formatR/inst/0000755000176200001440000000000014055436356012654 5ustar liggesusersformatR/inst/doc/0000755000176200001440000000000014055436356013421 5ustar liggesusersformatR/inst/doc/formatR.Rmd0000644000176200001440000002554214053624673015506 0ustar liggesusers--- title: formatR subtitle: Format R code automatically author: Yihui Xie date: "`r Sys.Date()`" show_toc: true slug: formatr githubEditURL: https://github.com/yihui/formatR/edit/master/vignettes/formatR.Rmd output: knitr:::html_vignette: toc: yes vignette: > %\VignetteEngine{knitr::rmarkdown} %\VignetteIndexEntry{An Introduction to formatR} --- ```{js, echo=FALSE} // redirect from CRAN to my personal website if (location.protocol === 'https:' && location.href.match('yihui.org') === null) location.href = 'https://yihui.org/formatr/'; ``` ```{r setup, include=FALSE} options(formatR.indent = 4, width = 70) knitr::opts_chunk$set(tidy = TRUE) ``` # 1. Installation You can install **formatR** from [CRAN](https://cran.r-project.org/package=formatR), or yihui.r-universe.dev if you want to test the latest development version: ```{r eval=FALSE} install.packages('formatR', repos = 'http://cran.rstudio.com') # or development version options(repos = c( yihui = 'https://yihui.r-universe.dev', CRAN = 'https://cloud.r-project.org' )) install.packages('formatR') ``` Or check out the [Github repository](https://github.com/yihui/formatR) and install from source if you know what this means. This page is always based on the development version. ```{r} library(formatR) sessionInfo() ``` # 2. Reformat R code The **formatR** package was designed to reformat R code to improve readability; the main workhorse is the function `tidy_source()`. Features include: - Long lines of code and comments are reorganized into appropriately shorter ones; - Spaces and indentation are added where necessary; - Comments are preserved in most cases; - The number of spaces to indent the code (i.e., tab width) can be specified (default is 4); - An `else` statement on a separate line without the leading `}` will be moved one line back; - `=` as an assignment operator can be replaced with `<-`; - The left brace `{` can be moved to a new line; - Arguments of a function call can start on a new line after the function name when they cannot fit on one line; - Lines can be wrapped after pipes (both magrittr pipes such as `%>%` and R's native pipe `|>` are supported). Below is an example of what `tidy_source()` can do. The source code is: ```{r example, eval=FALSE, tidy=FALSE} ## comments are retained; # a comment block will be reflowed if it contains long comments; #' roxygen comments will not be wrapped in any case 1+1 if(TRUE){ x=1 # inline comments }else{ x=2;print('Oh no... ask the right bracket to go away!')} 1*3 # one space before this comment will become two! 2+2+2 # only 'single quotes' are allowed in comments lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line ## here is a long long long long long long long long long long long long long comment that may be wrapped ``` We can copy the above code to clipboard, and type `tidy_source(width.cutoff = 50)` to get: ```{r example, eval=FALSE, tidy.opts=list(width.cutoff=50)} ``` Two applications of `tidy_source()`: - `tidy_dir()` can reformat all R scripts under a directory - `usage()` can reformat the usage of a function, e.g. compare `usage()` with the default output of `args()`: ```{r collapse=TRUE} library(formatR) usage(glm, width = 40) # can set arbitrary width here args(glm) ``` # 3. The Graphical User Interface If the **shiny** packages has been installed, the function `tidy_app()` can launch a Shiny app to reformat R code like this ([live demo](https://yihui.shinyapps.io/formatR/)): ``` {.r} formatR::tidy_app() ``` ```{r echo=FALSE, results='asis'} if (ignore_img <- !is.na(Sys.getenv('_R_CHECK_PACKAGE_NAME_', NA))) cat('') ``` # 4. Evaluate the code and mask output in comments It is often a pain when trying to copy R code from other people's code which has been run in R and the prompt characters (usually `>`) are attached in the beginning of code, because we have to remove all the prompts `>` and `+` manually before we are able to run the code. However, it will be convenient for the reader to understand the code if the output of the code can be attached. This motivates the function `tidy_eval()`, which uses `tidy_source()` to reformat the source code, evaluates the code in chunks, and attaches the output of each chunk as comments which will not actually break the original source code. Here is an example: ```{r comment=NA} set.seed(123) tidy_eval(text = c("a<-1+1;a # print the value", "matrix(rnorm(10),5)")) ``` The default source of the code is from clipboard like `tidy_source()`, so we can copy our code to clipboard, and simply run this in R: ```{r eval=FALSE} library(formatR) tidy_eval() # without specifying any arguments, it reads code from clipboard ``` # 5. Showcase We continue the example code in Section 2, using different arguments in `tidy_source()` such as `arrow`, `blank`, `indent`, `brace.newline` and `comment`, etc. ## Replace `=` with `<-` ```{r example, eval=FALSE, echo=5, tidy.opts=list(arrow=TRUE)} ``` ## Discard blank lines Note the 5th line (an empty line) was discarded: ```{r example, eval=FALSE, echo=1:5, tidy.opts=list(blank = FALSE)} ``` ## Reindent code (2 spaces instead of 4) ```{r example, eval=FALSE, echo=5, tidy.opts=list(indent = 2)} ``` ## Start function arguments on a new line With `args.newline = TRUE`, the example code below ```{r, args-code, eval=FALSE} shiny::updateSelectizeInput(session, "foo", label = "New Label", selected = c("A", "B"), choices = LETTERS, server = TRUE) ``` will be reformatted to: ```{r, args-code, eval=FALSE, tidy.opts=list(args.newline=TRUE)} ``` ## The pipe operators `%>%` and `|>` Since **formatR** 1.9, code lines contains operators `|>`, `%>%`, `%T%`, `%$%`, and/or `%<>%` will be automatically wrapped after these operators. For example, ```{r, pipe-code, eval=FALSE, tidy=FALSE} mtcars %>% subset(am == 0) %>% lm(mpg~hp, data=.) ``` will be reformatted to: ```{r, pipe-code, eval=FALSE, tidy=TRUE} ``` ## Move left braces `{` to new lines ```{r example, eval=FALSE, echo=5, tidy.opts=list(brace.newline = TRUE)} ``` ## Do not wrap comments ```{r example, eval=FALSE, echo=11:12, tidy.opts=list(wrap = FALSE)} ``` ## Discard comments ```{r example, eval=FALSE, tidy.opts=list(comment = FALSE, width.cutoff = 50)} ``` # 6. Further notes The tricks used in this packages are very dirty. There might be dangers in using the functions in **formatR**. Please read the next section carefully to know exactly how comments are preserved. The best strategy to avoid failure is to put comments in complete lines or after *complete* R expressions. Below are some known cases in which `tidy_source()` fails. ## Inline comments after an incomplete expression or ; ``` {.r} 1 + 2 + ## comments after an incomplete line 3 + 4 x <- ## this is not a complete expression 5 x <- 1; # you should not use ; here! ``` Code with comments after incomplete R expression cannot be reformatted by **formatR**. By the way, `tidy_source()` will move comments after `{` to the next line, e.g., ```{r comment-brace, tidy=FALSE, eval=FALSE} if (TRUE) {## comments } ``` will become ```{r comment-brace, eval=FALSE} ``` ## Inappropriate blank lines Blank lines are often used to separate complete chunks of R code, and arbitrary blank lines may cause failures in `tidy_source()` as well when the argument `blank = TRUE`, e.g. ``` {.r} if (TRUE) {'this is a BAD style of R programming!'} else 'failure!' ``` There should not be a blank line after the `if` statement. Of course `blank = FALSE` will not fail in this case. ## `?` with comments We can use the question mark (`?`) to view the help page, but **formatR** package is unable to correctly format the code using `?` with comments, e.g. ``` {.r} ?sd # help on sd() ``` In this case, it is recommended to use the function `help()` instead of the short-hand version `?`. # 7. How does `tidy_source()` actually work? In a nutshell, `tidy_source(text = code)` is basically `deparse(parse(text = code))`, but actually it is more complicated only because of one thing: `deparse()` drops comments, e.g., ```{r} deparse(parse(text = '1+2-3*4/5 # a comment')) ``` The method to preserve comments is to protect them as strings in R expressions. For example, there is a single line of comments in the source code: ``` {.r} # asdf ``` It will be first masked as ``` {.r} invisible(".IDENTIFIER1 # asdf.IDENTIFIER2") ``` which is a legal R expression, so `base::parse()` can deal with it and will no longer remove the disguised comments. In the end the identifiers will be removed to restore the original comments, i.e. the strings `invisible(".IDENTIFIER1` and `.IDENTIFIER2")` are replaced with empty strings. Inline comments are handled differently: two spaces will be added before the hash symbol `#`, e.g. ``` {.r} 1+1# comments ``` will become ``` {.r} 1+1 # comments ``` Inline comments are first disguised as a weird operation with its preceding R code, which is essentially meaningless but syntactically correct! For example, ``` {.r} 1+1 %\b% "# comments" ``` then `base::parse()` will deal with this expression; again, the disguised comments will not be removed. In the end, inline comments will be freed as well (remove the operator `%\b%` and surrounding double quotes). All these special treatments to comments are due to the fact that `base::parse()` and `base::deparse()` can tidy the R code at the price of dropping all the comments. # 8. Global options There are global options which can override some arguments in `tidy_source()`: | argument | global option | default | |-----------------|------------------------------------|--------------------| | `comment` | `options('formatR.comment')` | `TRUE` | | `blank` | `options('formatR.blank')` | `TRUE` | | `arrow` | `options('formatR.arrow')` | `FALSE` | | `indent` | `options('formatR.indent')` | `4` | | `wrap` | `options('formatR.wrap')` | `TRUE` | | `width.cutoff` | `options('formatR.width')` | `options('width')` | | `brace.newline` | `options('formatR.brace.newline')` | `FALSE` | | `args.newline` | `options('formatR.args.newline')` | `FALSE` | Also note that single lines of long comments will be wrapped into shorter ones automatically when `wrap = TRUE`, but roxygen comments will not be wrapped (i.e., comments that begin with `#'`). formatR/inst/doc/formatR.html0000644000176200001440000036337614055436356015743 0ustar liggesusers formatR

formatR

Format R code automatically

Yihui Xie

2021-06-01

1. Installation

You can install formatR from CRAN, or yihui.r-universe.dev if you want to test the latest development version:

install.packages("formatR", repos = "http://cran.rstudio.com")
# or development version
options(repos = c(yihui = "https://yihui.r-universe.dev", CRAN = "https://cloud.r-project.org"))
install.packages("formatR")

Or check out the Github repository and install from source if you know what this means. This page is always based on the development version.

library(formatR)
sessionInfo()
## R version 4.1.0 (2021-05-18)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur 10.16
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods  
## [7] base     
## 
## other attached packages:
## [1] formatR_1.11
## 
## loaded via a namespace (and not attached):
##  [1] digest_0.6.27     R6_2.5.0          jsonlite_1.7.2   
##  [4] magrittr_2.0.1    evaluate_0.14     rlang_0.4.11     
##  [7] stringi_1.6.2     jquerylib_0.1.4   bslib_0.2.5.1    
## [10] rmarkdown_2.8     tools_4.1.0       stringr_1.4.0    
## [13] xfun_0.23         yaml_2.2.1        compiler_4.1.0   
## [16] htmltools_0.5.1.1 knitr_1.33        sass_0.4.0

2. Reformat R code

The formatR package was designed to reformat R code to improve readability; the main workhorse is the function tidy_source(). Features include:

  • Long lines of code and comments are reorganized into appropriately shorter ones;
  • Spaces and indentation are added where necessary;
  • Comments are preserved in most cases;
  • The number of spaces to indent the code (i.e., tab width) can be specified (default is 4);
  • An else statement on a separate line without the leading } will be moved one line back;
  • = as an assignment operator can be replaced with <-;
  • The left brace { can be moved to a new line;
  • Arguments of a function call can start on a new line after the function name when they cannot fit on one line;
  • Lines can be wrapped after pipes (both magrittr pipes such as %>% and R’s native pipe |> are supported).

Below is an example of what tidy_source() can do. The source code is:

## comments are retained;
# a comment block will be reflowed if it contains long comments;
#' roxygen comments will not be wrapped in any case
1+1

if(TRUE){
x=1  # inline comments
}else{
x=2;print('Oh no... ask the right bracket to go away!')}
1*3 # one space before this comment will become two!
2+2+2    # only 'single quotes' are allowed in comments

lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)))  ### a linear model
1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1  # comment after a long line
## here is a long long long long long long long long long long long long long comment that may be wrapped

We can copy the above code to clipboard, and type tidy_source(width.cutoff = 50) to get:

## comments are retained; a comment block will be
## reflowed if it contains long comments;
#' roxygen comments will not be wrapped in any case
1 + 1

if (TRUE) {
    x = 1  # inline comments
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3  # one space before this comment will become two!
2 + 2 + 2  # only 'single quotes' are allowed in comments

lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100),
    x2 = rnorm(100)))  ### a linear model
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
    1 + 1 + 1 + 1 + 1 + 1 + 1 + 1  # comment after a long line
## here is a long long long long long long long
## long long long long long long comment that may
## be wrapped

Two applications of tidy_source():

  • tidy_dir() can reformat all R scripts under a directory

  • usage() can reformat the usage of a function, e.g. compare usage() with the default output of args():

    library(formatR)
    usage(glm, width = 40)  # can set arbitrary width here
    ## glm(formula, family = gaussian, data,
    ##     weights, subset, na.action,
    ##     start = NULL, etastart, mustart,
    ##     offset, control = list(...),
    ##     model = TRUE, method = "glm.fit",
    ##     x = FALSE, y = TRUE,
    ##     singular.ok = TRUE,
    ##     contrasts = NULL, ...)
    args(glm)
    ## function (formula, family = gaussian, data, weights, subset, 
    ##     na.action, start = NULL, etastart, mustart, offset, control = list(...), 
    ##     model = TRUE, method = "glm.fit", x = FALSE, y = TRUE, singular.ok = TRUE, 
    ##     contrasts = NULL, ...) 
    ## NULL

3. The Graphical User Interface

If the shiny packages has been installed, the function tidy_app() can launch a Shiny app to reformat R code like this (live demo):

formatR::tidy_app()

R source code before reformatting

After hitting the Format button:

R source code after reformatting

4. Evaluate the code and mask output in comments

It is often a pain when trying to copy R code from other people’s code which has been run in R and the prompt characters (usually >) are attached in the beginning of code, because we have to remove all the prompts > and + manually before we are able to run the code. However, it will be convenient for the reader to understand the code if the output of the code can be attached. This motivates the function tidy_eval(), which uses tidy_source() to reformat the source code, evaluates the code in chunks, and attaches the output of each chunk as comments which will not actually break the original source code. Here is an example:

set.seed(123)
tidy_eval(text = c("a<-1+1;a  # print the value", "matrix(rnorm(10),5)"))
a <- 1 + 1
a  # print the value
## [1] 2

matrix(rnorm(10), 5)
##             [,1]       [,2]
## [1,] -0.56047565  1.7150650
## [2,] -0.23017749  0.4609162
## [3,]  1.55870831 -1.2650612
## [4,]  0.07050839 -0.6868529
## [5,]  0.12928774 -0.4456620

The default source of the code is from clipboard like tidy_source(), so we can copy our code to clipboard, and simply run this in R:

library(formatR)
tidy_eval()
# without specifying any arguments, it reads code from clipboard

5. Showcase

We continue the example code in Section 2, using different arguments in tidy_source() such as arrow, blank, indent, brace.newline and comment, etc.

Replace = with <-

if (TRUE) {
    x <- 1  # inline comments
} else {
    x <- 2
    print("Oh no... ask the right bracket to go away!")
}

Discard blank lines

Note the 5th line (an empty line) was discarded:

## comments are retained; a comment block will be reflowed if it
## contains long comments;
#' roxygen comments will not be wrapped in any case
1 + 1
if (TRUE) {
    x = 1  # inline comments
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3  # one space before this comment will become two!

Reindent code (2 spaces instead of 4)

if (TRUE) {
  x = 1  # inline comments
} else {
  x = 2
  print("Oh no... ask the right bracket to go away!")
}

Start function arguments on a new line

With args.newline = TRUE, the example code below

shiny::updateSelectizeInput(session, "foo", label = "New Label", selected = c("A",
    "B"), choices = LETTERS, server = TRUE)

will be reformatted to:

shiny::updateSelectizeInput(
    session, "foo", label = "New Label", selected = c("A", "B"),
    choices = LETTERS, server = TRUE
)

The pipe operators %>% and |>

Since formatR 1.9, code lines contains operators |>, %>%, %T%, %$%, and/or %<>% will be automatically wrapped after these operators. For example,

mtcars %>% subset(am == 0) %>% lm(mpg~hp, data=.)

will be reformatted to:

mtcars %>%
    subset(am == 0) %>%
    lm(mpg ~ hp, data = .)

Move left braces { to new lines

if (TRUE)
{
    x = 1  # inline comments
} else
{
    x = 2
    print("Oh no... ask the right bracket to go away!")
}

Do not wrap comments

1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
    1 + 1 + 1  # comment after a long line
## here is a long long long long long long long long long long long long long comment that may be wrapped

Discard comments

1 + 1
if (TRUE) {
    x = 1
} else {
    x = 2
    print("Oh no... ask the right bracket to go away!")
}
1 * 3
2 + 2 + 2
lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100),
    x2 = rnorm(100)))
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +
    1 + 1 + 1 + 1 + 1 + 1 + 1 + 1

6. Further notes

The tricks used in this packages are very dirty. There might be dangers in using the functions in formatR. Please read the next section carefully to know exactly how comments are preserved. The best strategy to avoid failure is to put comments in complete lines or after complete R expressions. Below are some known cases in which tidy_source() fails.

Inline comments after an incomplete expression or ;

1 + 2 + ## comments after an incomplete line
    3 + 4
x <- ## this is not a complete expression
     5
x <- 1; # you should not use ; here!

Code with comments after incomplete R expression cannot be reformatted by formatR. By the way, tidy_source() will move comments after { to the next line, e.g.,

if (TRUE) {## comments
}

will become

if (TRUE) {
    ## comments
}

Inappropriate blank lines

Blank lines are often used to separate complete chunks of R code, and arbitrary blank lines may cause failures in tidy_source() as well when the argument blank = TRUE, e.g.

if (TRUE)

{'this is a BAD style of R programming!'} else 'failure!'

There should not be a blank line after the if statement. Of course blank = FALSE will not fail in this case.

? with comments

We can use the question mark (?) to view the help page, but formatR package is unable to correctly format the code using ? with comments, e.g.

?sd  # help on sd()

In this case, it is recommended to use the function help() instead of the short-hand version ?.

7. How does tidy_source() actually work?

In a nutshell, tidy_source(text = code) is basically deparse(parse(text = code)), but actually it is more complicated only because of one thing: deparse() drops comments, e.g.,

deparse(parse(text = "1+2-3*4/5 # a comment"))
## [1] "expression(1 + 2 - 3 * 4/5)"

The method to preserve comments is to protect them as strings in R expressions. For example, there is a single line of comments in the source code:

  # asdf

It will be first masked as

invisible(".IDENTIFIER1  # asdf.IDENTIFIER2")

which is a legal R expression, so base::parse() can deal with it and will no longer remove the disguised comments. In the end the identifiers will be removed to restore the original comments, i.e. the strings invisible(".IDENTIFIER1 and .IDENTIFIER2") are replaced with empty strings.

Inline comments are handled differently: two spaces will be added before the hash symbol #, e.g.

1+1#  comments

will become

1+1  #  comments

Inline comments are first disguised as a weird operation with its preceding R code, which is essentially meaningless but syntactically correct! For example,

1+1 %\b% "#  comments"

then base::parse() will deal with this expression; again, the disguised comments will not be removed. In the end, inline comments will be freed as well (remove the operator %\b% and surrounding double quotes).

All these special treatments to comments are due to the fact that base::parse() and base::deparse() can tidy the R code at the price of dropping all the comments.

8. Global options

There are global options which can override some arguments in tidy_source():

argument global option default
comment options('formatR.comment') TRUE
blank options('formatR.blank') TRUE
arrow options('formatR.arrow') FALSE
indent options('formatR.indent') 4
wrap options('formatR.wrap') TRUE
width.cutoff options('formatR.width') options('width')
brace.newline options('formatR.brace.newline') FALSE
args.newline options('formatR.args.newline') FALSE

Also note that single lines of long comments will be wrapped into shorter ones automatically when wrap = TRUE, but roxygen comments will not be wrapped (i.e., comments that begin with #').

formatR/inst/doc/formatR.R0000644000176200001440000002022414055436354015154 0ustar liggesusers## ----setup, include=FALSE------------------------------------------- options(formatR.indent = 4, width = 70) knitr::opts_chunk$set(tidy = TRUE) ## ----eval=FALSE----------------------------------------------------- # install.packages('formatR', repos = 'http://cran.rstudio.com') # # or development version # options(repos = c( # yihui = 'https://yihui.r-universe.dev', # CRAN = 'https://cloud.r-project.org' # )) # install.packages('formatR') ## ------------------------------------------------------------------- library(formatR) sessionInfo() ## ----example, eval=FALSE, tidy=FALSE-------------------------------- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----example, eval=FALSE, tidy.opts=list(width.cutoff=50)----------- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----collapse=TRUE-------------------------------------------------- library(formatR) usage(glm, width = 40) # can set arbitrary width here args(glm) ## ----echo=FALSE, results='asis'------------------------------------- if (ignore_img <- !is.na(Sys.getenv('_R_CHECK_PACKAGE_NAME_', NA))) cat('') ## ----comment=NA----------------------------------------------------- set.seed(123) tidy_eval(text = c("a<-1+1;a # print the value", "matrix(rnorm(10),5)")) ## ----eval=FALSE----------------------------------------------------- # library(formatR) # tidy_eval() # # without specifying any arguments, it reads code from clipboard ## ----example, eval=FALSE, echo=5, tidy.opts=list(arrow=TRUE)-------- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----example, eval=FALSE, echo=1:5, tidy.opts=list(blank = FALSE)---- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----example, eval=FALSE, echo=5, tidy.opts=list(indent = 2)-------- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ---- args-code, eval=FALSE----------------------------------------- # shiny::updateSelectizeInput(session, "foo", label = "New Label", # selected = c("A", "B"), choices = LETTERS, # server = TRUE) ## ---- args-code, eval=FALSE, tidy.opts=list(args.newline=TRUE)------ # shiny::updateSelectizeInput(session, "foo", label = "New Label", # selected = c("A", "B"), choices = LETTERS, # server = TRUE) ## ---- pipe-code, eval=FALSE, tidy=FALSE----------------------------- # mtcars %>% subset(am == 0) %>% lm(mpg~hp, data=.) ## ---- pipe-code, eval=FALSE, tidy=TRUE------------------------------ # mtcars %>% subset(am == 0) %>% lm(mpg~hp, data=.) ## ----example, eval=FALSE, echo=5, tidy.opts=list(brace.newline = TRUE)---- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----example, eval=FALSE, echo=11:12, tidy.opts=list(wrap = FALSE)---- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----example, eval=FALSE, tidy.opts=list(comment = FALSE, width.cutoff = 50)---- # ## comments are retained; # # a comment block will be reflowed if it contains long comments; # #' roxygen comments will not be wrapped in any case # 1+1 # # if(TRUE){ # x=1 # inline comments # }else{ # x=2;print('Oh no... ask the right bracket to go away!')} # 1*3 # one space before this comment will become two! # 2+2+2 # only 'single quotes' are allowed in comments # # lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model # 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 # comment after a long line # ## here is a long long long long long long long long long long long long long comment that may be wrapped ## ----comment-brace, tidy=FALSE, eval=FALSE-------------------------- # if (TRUE) {## comments # } ## ----comment-brace, eval=FALSE-------------------------------------- # if (TRUE) {## comments # } ## ------------------------------------------------------------------- deparse(parse(text = '1+2-3*4/5 # a comment')) formatR/inst/format/0000755000176200001440000000000013357673706014152 5ustar liggesusersformatR/inst/format/messy.R0000644000176200001440000000105014024034271015405 0ustar liggesusers # a single line of comments is preserved 1+1 if(TRUE){ x=1 # inline comments }else{ x=2;print('Oh no... ask the right bracket to go away!')} 1*3 # one space before this comment will become two! 2+2+2 # 'short comments' # only 'single quotes' are allowed in comments df=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)) lm(y~x1+x2, data=df) 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 ## comments after a long line ## here is a long long long long long long long long long long long long long long long long long long long long comment formatR/inst/shiny/0000755000176200001440000000000014055436356014006 5ustar liggesusersformatR/inst/shiny/Readme.md0000644000176200001440000000036312376444004015520 0ustar liggesusersThis app uses a `textarea` input to store R code, which is reformatted by `formatR::tidy_source()`. The result is written back in the text box. Click the `demo` button to load a demo, or paste your own R code here to see what this app can do. formatR/inst/shiny/DESCRIPTION0000644000176200001440000000021414024176674015511 0ustar liggesusersTitle: Tidy R code using formatR Author: Yihui Xie AuthorUrl: http://yihui.org License: MIT DisplayMode: Showcase Tags: formatR Type: Shiny formatR/inst/shiny/server.R0000644000176200001440000000116014053766434015436 0ustar liggesuserslibrary(shiny) shinyServer(function(input, output, session) { observe({ res = try(formatR::tidy_source( text = input$src, output = FALSE, comment = input$arg_comment, blank = input$arg_blank, arrow = input$arg_assign, brace.newline = input$arg_brace, indent = input$arg_indent, args.newline = input$arg_anl, wrap = input$arg_wrap, width.cutoff = if (input$width_type == 'minimum') input$arg_width else I(input$arg_width) )) session$sendCustomMessage( 'replace_textarea', if (inherits(res, 'try-error')) I(res) else paste(res$text.tidy, collapse = '\n') ) }) }) formatR/inst/shiny/www/0000755000176200001440000000000013357673706014640 5ustar liggesusersformatR/inst/shiny/www/shiny-handler.js0000644000176200001440000000140012330114006017703 0ustar liggesusersShiny.addCustomMessageHandler("replace_textarea", function(message) { if (typeof message === 'object') { // an error must have occurred var msg = '
'
                + message[0] + '
'; $('textarea#src').popover('destroy') .popover({ placement: 'bottom', html: true, title: 'Failed to format the code', content: msg }) .popover('show') .addClass('alert-error'); } else { $('textarea#src').popover('destroy').removeClass('alert-error').val(message); }; } ); formatR/inst/shiny/ui.R0000644000176200001440000000430214053767733014551 0ustar liggesuserslibrary(shiny) shinyUI(fluidPage( title = 'Tidy R Code with formatR (Yihui Xie)', helpText(), # just a placeholder for a little bit top margin sidebarLayout( sidebarPanel( tags$head( tags$script(src = 'shiny-handler.js'), tags$style(type = 'text/css', '.popover {max-width: 100%;}') ), helpText('This Shiny app uses the function', code('tidy_source()'), 'in the', a(href = 'https://yihui.org/formatR/', strong('formatR')), sprintf('(>= v%s)', packageVersion('formatR')), 'package to reformat R code in the text box on the right.', a(list(icon('hand-o-right'), 'demo'), class = 'btn btn-small btn-info', onclick = '$("textarea#src").val($("#demo").val()).trigger("change");')), checkboxInput('arg_comment', 'Preserve comments', TRUE), checkboxInput('arg_blank', 'Preserve blank lines', TRUE), checkboxInput('arg_assign', 'Replace = with <-', FALSE), checkboxInput('arg_anl', 'Start function arguments on a new line', FALSE), checkboxInput('arg_brace', 'Put { on a new line', FALSE), checkboxInput('arg_wrap', 'Wrap comments', TRUE), numericInput ('arg_indent', 'Number of spaces for indentation', 4, min = 0), radioButtons('width_type', 'Line width type', c('minimum', 'maximum'), inline = TRUE), numericInput ('arg_width', 'Line width value', 70, min = 20, max = 500), submitButton ('Format My Code', icon('toggle-right')) ), mainPanel( tags$textarea( id = 'src', rows = 20, style = 'width: 99%; font-family: monospace; word-wrap: normal; white-space: pre;', placeholder = 'paste your R code here...' ), tags$textarea( id = 'demo', style = 'display: none;', paste(c( readLines(system.file('format', 'messy.R', package = 'formatR')), if (getRversion() >= '4.1.0') c( '', "# R's native pipe on a single line", 'mtcars |> subset(am==0) |> (\\(.) lm(mpg~hp, data=.))()' ), '', '# magrittr pipes on a single line', 'mtcars %>% subset(am==0) %>% lm(mpg~hp, data=.)' ), collapse = '\n') ) ) ) )) formatR/inst/NEWS.Rd0000644000176200001440000000043714052075675013723 0ustar liggesusers\name{NEWS} \title{News for Package 'formatR'} \section{CHANGES IN formatR VERSION 999.999}{ \itemize{ \item This NEWS file is only a placeholder. The version 999.999 does not really exist. Please read the NEWS on Github: \url{https://github.com/yihui/formatR/releases} } }