├── .gitignore ├── tests ├── test-all.R └── testit │ ├── test-utils.R │ └── test-tidy.R ├── .Rbuildignore ├── inst ├── shiny │ ├── DESCRIPTION │ ├── Readme.md │ ├── server.R │ ├── www │ │ └── shiny-handler.js │ └── ui.R ├── format │ └── messy.R └── examples │ └── tidy.source.R ├── NAMESPACE ├── README.md ├── R ├── shiny.R ├── eval.R ├── usage.R ├── utils.R └── tidy.R ├── formatR.Rproj ├── man ├── tidy_app.Rd ├── usage.Rd ├── tidy_dir.Rd ├── tidy_eval.Rd └── tidy_source.Rd ├── .travis.yml ├── DESCRIPTION ├── vignettes └── formatR.Rmd └── NEWS /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | shinyapps/ 3 | .Rhistory 4 | -------------------------------------------------------------------------------- /tests/test-all.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | test_pkg('formatR') 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | inst/examples 2 | .gitignore 3 | ^\.Rproj\.user$ 4 | ^.*\.Rproj$ 5 | .travis.yml 6 | inst/shiny/shinyapps 7 | .Rhistory 8 | -------------------------------------------------------------------------------- /inst/shiny/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Title: Tidy R code using formatR 2 | Author: Yihui Xie 3 | AuthorUrl: http://yihui.name 4 | License: MIT 5 | DisplayMode: Showcase 6 | Tags: formatR 7 | Type: Shiny 8 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(tidy_app) 4 | export(tidy_dir) 5 | export(tidy_eval) 6 | export(tidy_file) 7 | export(tidy_source) 8 | export(usage) 9 | import(utils) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # formatR 2 | 3 | [![Build Status](https://travis-ci.org/yihui/formatR.svg)](https://travis-ci.org/yihui/formatR) 4 | 5 | Format R code automatically. 6 | 7 | See the package homepage for more information. 8 | -------------------------------------------------------------------------------- /inst/shiny/Readme.md: -------------------------------------------------------------------------------- 1 | This app uses a `textarea` input to store R code, which is reformatted by 2 | `formatR::tidy_source()`. The result is written back in the text box. Click the 3 | `demo` button to load a demo, or paste your own R code here to see what this app 4 | can do. 5 | -------------------------------------------------------------------------------- /R/shiny.R: -------------------------------------------------------------------------------- 1 | #' A Shiny app to format R code 2 | #' 3 | #' This function calls \code{\link{tidy_source}()} to format R code in a Shiny 4 | #' app. The arguments of \code{tidy_source()} are presented in the app as input 5 | #' widgets such as checkboxes. 6 | #' @export 7 | #' @examples if (interactive()) formatR::tidy_app() 8 | tidy_app = function() { 9 | shiny::runApp(system.file('shiny', package = 'formatR')) 10 | } 11 | -------------------------------------------------------------------------------- /formatR.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageInstallArgs: -v && Rscript -e "Rd2roxygen::rab(install=TRUE)" 20 | PackageCheckArgs: --no-manual --as-cran 21 | -------------------------------------------------------------------------------- /man/tidy_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/shiny.R 3 | \name{tidy_app} 4 | \alias{tidy_app} 5 | \title{A Shiny app to format R code} 6 | \usage{ 7 | tidy_app() 8 | } 9 | \description{ 10 | This function calls \code{\link{tidy_source}()} to format R code in a Shiny 11 | app. The arguments of \code{tidy_source()} are presented in the app as input 12 | widgets such as checkboxes. 13 | } 14 | \examples{ 15 | if (interactive()) formatR::tidy_app() 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | sudo: false 3 | cache: packages 4 | 5 | env: 6 | global: 7 | - R_PKG="$(basename $TRAVIS_REPO_SLUG)" 8 | - secure: c1BEcWGUMERYBFDkkVC28rHQQtLOz508FToM5IX/rDlvRU/CTMZhok4QmZfvJhQIs1jC6vIka4KZf+6Uibkn1D7BIP3fAorUTSOa3Sw3qT99jnGFNu5SB+0huUx1CxtT8StjmA00k8Hq7qmmVF4sW8UxdxBeGQ0rF47knbSf6HU= 9 | 10 | repos: 11 | XRAN: https://yihui.name/xran 12 | 13 | before_install: 14 | - "curl -L https://yihui.name/xran/.gitconfig -o ~/.gitconfig" 15 | 16 | after_success: 17 | - "(curl -L https://yihui.name/xran/r-xran | bash)" 18 | -------------------------------------------------------------------------------- /inst/shiny/server.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(formatR) 3 | 4 | shinyServer(function(input, output, session) { 5 | observe({ 6 | res = try(tidy_source( 7 | text = input$src, output = FALSE, comment = input$arg_comment, 8 | blank = input$arg_blank, arrow = input$arg_assign, 9 | brace.newline = input$arg_brace, indent = input$arg_indent, 10 | width.cutoff = input$arg_width 11 | )) 12 | session$sendCustomMessage( 13 | 'replace_textarea', 14 | if (inherits(res, 'try-error')) I(res) else paste(res$text.tidy, collapse = '\n') 15 | ) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /inst/format/messy.R: -------------------------------------------------------------------------------- 1 | # a single line of comments is preserved 2 | 1+1 3 | 4 | if(TRUE){ 5 | x=1 # inline comments 6 | }else{ 7 | x=2;print('Oh no... ask the right bracket to go away!')} 8 | 1*3 # one space before this comment will become two! 9 | 2+2+2 # 'short comments' 10 | 11 | # only 'single quotes' are allowed in comments 12 | df=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)) 13 | lm(y~x1+x2, data=df) 14 | 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 15 | 16 | ## here is a long long long long long long long long long long long long long long long long long long long long comment 17 | -------------------------------------------------------------------------------- /inst/shiny/www/shiny-handler.js: -------------------------------------------------------------------------------- 1 | Shiny.addCustomMessageHandler("replace_textarea", 2 | function(message) { 3 | if (typeof message === 'object') { 4 | // an error must have occurred 5 | var msg = '
'
 6 |                 + message[0] + '
'; 7 | $('textarea#src').popover('destroy') 8 | .popover({ 9 | placement: 'bottom', 10 | html: true, 11 | title: 'Failed to format the code', 12 | content: msg 13 | }) 14 | .popover('show') 15 | .addClass('alert-error'); 16 | } else { 17 | $('textarea#src').popover('destroy').removeClass('alert-error').val(message); 18 | }; 19 | } 20 | ); 21 | -------------------------------------------------------------------------------- /tests/testit/test-utils.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | assert( 4 | 'move_leftbrace() works', 5 | # no indent before abc, so no indent before { 6 | identical(move_leftbrace('abc() {\n }'), 'abc()\n{\n }'), 7 | # 3 spaces before abc, 3 before { 8 | identical(move_leftbrace(' a() {\n}'), c(' a()\n {\n}')), 9 | # blank lines are not removed 10 | identical(move_leftbrace(c('a', '', 'b')), c('a', '', 'b')), 11 | identical(move_leftbrace('if (TRUE) {\n if (FALSE) {\n 1\n }\n}'), 12 | 'if (TRUE)\n{\n if (FALSE)\n {\n 1\n }\n}'), 13 | identical(move_leftbrace('if (TRUE) {\n 1\n} else {\n 2}'), 14 | 'if (TRUE)\n{\n 1\n} else\n{\n 2}') 15 | ) 16 | 17 | assert( 18 | 'reindent_lines() works', 19 | identical(reindent_lines(''), ''), 20 | identical(reindent_lines(c('', '')), c('', '')), 21 | identical(reindent_lines(' ', 2), ' '), 22 | identical(reindent_lines('if (TRUE) {\n 1\n}', 2), 'if (TRUE) {\n 1\n}') 23 | ) 24 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: formatR 2 | Type: Package 3 | Title: Format R Code Automatically 4 | Version: 1.4.2 5 | Date: 2016-05-09 6 | Authors@R: c( 7 | person("Yihui", "Xie", email = "xie@yihui.name", role = c("aut", "cre")), 8 | person("Kohske", "Takahashi", role = "ctb"), 9 | person("Ed", "Lee", role = "ctb") 10 | ) 11 | Maintainer: Yihui Xie 12 | Description: Provides a function tidy_source() to format R source code. Spaces 13 | and indent will be added to the code automatically, and comments will be 14 | preserved under certain conditions, so that R code will be more 15 | human-readable and tidy. There is also a Shiny app as a user interface in 16 | this package (see tidy_app()). 17 | Depends: 18 | R (>= 3.0.2) 19 | Suggests: 20 | codetools, 21 | shiny, 22 | testit, 23 | rmarkdown, 24 | knitr 25 | License: GPL 26 | URL: http://yihui.name/formatR 27 | BugReports: https://github.com/yihui/formatR/issues 28 | VignetteBuilder: knitr 29 | RoxygenNote: 6.0.0 30 | -------------------------------------------------------------------------------- /man/usage.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/usage.R 3 | \name{usage} 4 | \alias{usage} 5 | \title{Show the usage of a function} 6 | \usage{ 7 | usage(FUN, width = getOption("width"), tidy = TRUE, output = TRUE) 8 | } 9 | \arguments{ 10 | \item{FUN}{the function name} 11 | 12 | \item{width}{the width of output (passed to \code{width.cutoff} in 13 | \code{\link{tidy_source}})} 14 | 15 | \item{tidy}{whether to reformat the usage code} 16 | 17 | \item{output}{whether to write the output to the console (via 18 | \code{\link{cat}})} 19 | } 20 | \value{ 21 | The R code for the usage is returned as a character string 22 | (invisibly). 23 | } 24 | \description{ 25 | Print the reformatted usage of a function. The arguments of the function are 26 | searched by \code{\link{argsAnywhere}}, so the function can be either 27 | exported or non-exported in a package. S3 methods will be marked. 28 | } 29 | \examples{ 30 | library(formatR) 31 | usage(var) 32 | 33 | usage(plot) 34 | 35 | usage(plot.default) # default method 36 | usage("plot.lm") # on the 'lm' class 37 | 38 | usage(usage) 39 | 40 | usage(barplot.default, width = 60) # narrower output 41 | } 42 | \seealso{ 43 | \code{\link{tidy_source}} 44 | } 45 | -------------------------------------------------------------------------------- /inst/examples/tidy.source.R: -------------------------------------------------------------------------------- 1 | library(formatR) 2 | 3 | ## a messy R script 4 | messy = system.file('format', 'messy.R', package = 'formatR') 5 | tidy_source(messy) 6 | 7 | ## use the 'text' argument 8 | src = readLines(messy) 9 | 10 | ## source code 11 | cat(src, sep = '\n') 12 | 13 | ## the formatted version 14 | tidy_source(text = src) 15 | 16 | ## preserve blank lines 17 | tidy_source(text = src, blank = TRUE) 18 | 19 | ## indent with 2 spaces 20 | tidy_source(text = src, indent = 2) 21 | 22 | ## discard comments! 23 | tidy_source(text = src, comment = FALSE) 24 | 25 | ## wanna see the gory truth?? 26 | tidy_source(text = src, output = FALSE)$text.mask 27 | 28 | 29 | ## tidy up the source code of image demo 30 | x = file.path(system.file(package = "graphics"), "demo", "image.R") 31 | 32 | # to console 33 | tidy_source(x) 34 | 35 | # to a file 36 | f = tempfile() 37 | tidy_source(x, blank = TRUE, file = f) 38 | 39 | ## check the original code here and see the difference 40 | file.show(x) 41 | file.show(f) 42 | 43 | ## use global options 44 | options(comment = TRUE, blank = FALSE) 45 | tidy_source(x) 46 | 47 | ## if you've copied R code into the clipboard 48 | if (interactive()) { 49 | tidy_source("clipboard") 50 | ## write into clipboard again 51 | tidy_source("clipboard", file = "clipboard") 52 | } 53 | 54 | ## the if-else structure 55 | tidy_source(text=c('{if(TRUE)1 else 2; if(FALSE){1+1',"## comments",'} else 2}')) 56 | -------------------------------------------------------------------------------- /man/tidy_dir.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidy.R 3 | \name{tidy_dir} 4 | \alias{tidy_dir} 5 | \alias{tidy_file} 6 | \title{Format all R scripts under a directory, or specified R scripts} 7 | \usage{ 8 | tidy_dir(path = ".", recursive = FALSE, ...) 9 | 10 | tidy_file(file, ...) 11 | } 12 | \arguments{ 13 | \item{path}{the directory} 14 | 15 | \item{recursive}{whether to recursively look for R scripts under \code{path}} 16 | 17 | \item{...}{other arguments to be passed to \code{\link{tidy_source}}} 18 | 19 | \item{file}{a vector of filenames} 20 | } 21 | \value{ 22 | Invisible \code{NULL}. 23 | } 24 | \description{ 25 | \code{tidy_dir()} first looks for all the R scripts under a directory (using 26 | the pattern \code{"[.][RrSsQq]$"}), then uses \code{\link{tidy_source}} to 27 | tidy these scripts. The original scripts will be overwritten with reformatted 28 | code if reformatting was successful. You may need to back up the original 29 | directory first if you do not fully understand the tricks used by 30 | \code{\link{tidy_source}}. \code{tidy_file()} formats specified R scripts. 31 | } 32 | \examples{ 33 | library(formatR) 34 | 35 | path = tempdir() 36 | file.copy(system.file("demo", package = "base"), path, recursive = TRUE) 37 | tidy_dir(path, recursive = TRUE) 38 | } 39 | \seealso{ 40 | \code{\link{tidy_source}} 41 | } 42 | \author{ 43 | Yihui Xie (\code{tidy_dir}) and Ed Lee (\code{tidy_file}) 44 | } 45 | -------------------------------------------------------------------------------- /man/tidy_eval.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/eval.R 3 | \name{tidy_eval} 4 | \alias{tidy_eval} 5 | \title{Evaluate R code and mask the output by a prefix} 6 | \usage{ 7 | tidy_eval(source = "clipboard", ..., file = "", prefix = "## ", envir = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{source}{the input filename (by default the clipboard; see 11 | \code{\link{tidy_source}})} 12 | 13 | \item{...}{other arguments passed to \code{\link{tidy_source}}} 14 | 15 | \item{file}{the file to write by \code{\link{cat}}; by default the output is 16 | printed on screen} 17 | 18 | \item{prefix}{the prefix to mask the output} 19 | 20 | \item{envir}{the environment in which to evaluate the code (by default the 21 | parent environment; if we do not want to mess up with the parent 22 | environment, we can set \code{envir = NULL} or \code{envir = new.env()})} 23 | } 24 | \value{ 25 | Evaluated R code with corresponding output (printed on screen or 26 | written in a file). 27 | } 28 | \description{ 29 | This function is designed to insert the output of each chunk of R code into 30 | the source code without really breaking the source code, since the output is 31 | masked in comments. 32 | } 33 | \examples{ 34 | library(formatR) 35 | ## evaluate simple code as a character vector 36 | tidy_eval(text = c("a<-1+1;a", "matrix(rnorm(10),5)")) 37 | 38 | ## evaluate a file 39 | tidy_eval(file.path(system.file(package = "stats"), "demo", "nlm.R")) 40 | } 41 | \references{ 42 | \url{http://yihui.name/formatR} 43 | } 44 | -------------------------------------------------------------------------------- /R/eval.R: -------------------------------------------------------------------------------- 1 | #' Evaluate R code and mask the output by a prefix 2 | #' 3 | #' This function is designed to insert the output of each chunk of R code into 4 | #' the source code without really breaking the source code, since the output is 5 | #' masked in comments. 6 | #' @param source the input filename (by default the clipboard; see 7 | #' \code{\link{tidy_source}}) 8 | #' @param ... other arguments passed to \code{\link{tidy_source}} 9 | #' @param file the file to write by \code{\link{cat}}; by default the output is 10 | #' printed on screen 11 | #' @param prefix the prefix to mask the output 12 | #' @param envir the environment in which to evaluate the code (by default the 13 | #' parent environment; if we do not want to mess up with the parent 14 | #' environment, we can set \code{envir = NULL} or \code{envir = new.env()}) 15 | #' @return Evaluated R code with corresponding output (printed on screen or 16 | #' written in a file). 17 | #' @export 18 | #' @references \url{http://yihui.name/formatR} 19 | #' @examples library(formatR) 20 | #' ## evaluate simple code as a character vector 21 | #' tidy_eval(text = c('a<-1+1;a','matrix(rnorm(10),5)')) 22 | #' 23 | #' ## evaluate a file 24 | #' tidy_eval(file.path(system.file(package = 'stats'), 'demo', 'nlm.R')) 25 | tidy_eval = function(source = 'clipboard', ..., file = '', prefix = '## ', envir = parent.frame()) { 26 | txt = tidy_source(source, ..., output = FALSE)$text.tidy 27 | for(i in 1:length(txt)) { 28 | cat(txt[i], '\n', sep = '', file = file, append = TRUE) 29 | out = capture.output(eval(res <- parse_only(txt[i]), envir = envir)) 30 | if (length(res) > 0L && length(out) > 0L) { 31 | cat(paste(prefix, out, sep = ''), sep = '\n', file = file, append = TRUE) 32 | cat('\n', file = file, append = TRUE) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /inst/shiny/ui.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | shinyUI(fluidPage( 4 | title = 'Tidy R Code with formatR (Yihui Xie)', 5 | helpText(), # just a placeholder for a little bit top margin 6 | sidebarLayout( 7 | sidebarPanel( 8 | tags$head( 9 | tags$script(src = 'shiny-handler.js'), 10 | tags$style(type = 'text/css', '.popover {max-width: 100%;}') 11 | ), 12 | helpText('This Shiny app uses the function', code('tidy_source()'), 13 | 'in the', a(href = 'http://yihui.name/formatR', strong('formatR')), 14 | sprintf('(>= v%s)', packageVersion('formatR')), 15 | 'package to reformat R code in the text box on the right.', 16 | a(list(icon('hand-o-right'), 'demo'), class = 'btn btn-small btn-info', 17 | onclick = '$("textarea#src").val($("#demo").val()).trigger("change");')), 18 | checkboxInput('arg_comment', 'Preserve comments', TRUE), 19 | checkboxInput('arg_blank', 'Preserve blank lines', TRUE), 20 | checkboxInput('arg_assign', 'Replace = with <-', FALSE), 21 | checkboxInput('arg_brace', 'Put { on a new line', FALSE), 22 | numericInput ('arg_indent', 'Number of spaces for indentation', 4, min = 0), 23 | numericInput ('arg_width', 'Minimum line width', 70, min = 20, max = 500), 24 | submitButton ('Tidy My Code', icon('toggle-right')) 25 | ), 26 | mainPanel( 27 | tags$textarea( 28 | id = 'src', rows = 20, 29 | style = 'width: 99%; font-family: monospace; word-wrap: normal; white-space: pre;', 30 | placeholder = 'paste your R code here...' 31 | ), 32 | tags$textarea( 33 | id = 'demo', style = 'display: none;', 34 | paste( 35 | readLines(system.file('format', 'messy.R', package = 'formatR')), 36 | collapse = '\n' 37 | ) 38 | ) 39 | ) 40 | ) 41 | )) 42 | -------------------------------------------------------------------------------- /R/usage.R: -------------------------------------------------------------------------------- 1 | #' Show the usage of a function 2 | #' 3 | #' Print the reformatted usage of a function. The arguments of the function are 4 | #' searched by \code{\link{argsAnywhere}}, so the function can be either 5 | #' exported or non-exported in a package. S3 methods will be marked. 6 | #' @param FUN the function name 7 | #' @param width the width of output (passed to \code{width.cutoff} in 8 | #' \code{\link{tidy_source}}) 9 | #' @param tidy whether to reformat the usage code 10 | #' @param output whether to write the output to the console (via 11 | #' \code{\link{cat}}) 12 | #' @return The R code for the usage is returned as a character string 13 | #' (invisibly). 14 | #' @seealso \code{\link{tidy_source}} 15 | #' @export 16 | #' @examples library(formatR) 17 | #' usage(var) 18 | #' 19 | #' usage(plot) 20 | #' 21 | #' usage(plot.default) # default method 22 | #' usage('plot.lm') # on the 'lm' class 23 | #' 24 | #' usage(usage) 25 | #' 26 | #' usage(barplot.default, width = 60) # narrower output 27 | usage = function(FUN, width = getOption('width'), tidy = TRUE, output = TRUE) { 28 | fn = as.character(substitute(FUN)) 29 | res = capture.output(if (is.function(FUN)) args(FUN) else { 30 | do.call(argsAnywhere, list(fn)) 31 | }) 32 | if (identical(res, 'NULL')) return() 33 | res[1] = substring(res[1], 9) # rm 'function ' in the beginning 34 | isS3 = FALSE 35 | if (length(fn) == 3 && (fn[1] %in% c('::', ':::'))) fn = fn[3] 36 | if (grepl('.', fn, fixed = TRUE)) { 37 | n = length(parts <- strsplit(fn, '.', fixed = TRUE)[[1]]) 38 | for (i in 2:n) { 39 | gen = paste(parts[1L:(i - 1)], collapse = ".") 40 | cl = paste(parts[i:n], collapse = ".") 41 | if (gen == "" || cl == "") next 42 | if (!is.null(f <- getS3method(gen, cl, TRUE)) && !is.null(environment(f))) { 43 | res[1] = paste(gen, res[1]) 44 | header = if (cl == 'default') 45 | '## Default S3 method:' else sprintf("## S3 method for class '%s'", cl) 46 | res = c(header, res) 47 | isS3 = TRUE 48 | break 49 | } 50 | } 51 | } 52 | if (!isS3) res[1] = paste(fn, res[1]) 53 | if ((n <- length(res)) > 1 && res[n] == 'NULL') res = res[-n] # rm last element 'NULL' 54 | if (!tidy) { 55 | if (output) cat(res, sep = '\n') 56 | return(invisible(res)) 57 | } 58 | 59 | tidy.res = tidy_source(text = res, output = FALSE, width.cutoff = width) 60 | if (output) cat(tidy.res$text.tidy, sep = '\n') 61 | invisible(tidy.res$text.tidy) 62 | } 63 | -------------------------------------------------------------------------------- /tests/testit/test-tidy.R: -------------------------------------------------------------------------------- 1 | library(testit) 2 | 3 | tidy.res = function(x, ...) { 4 | tidy_source(text = x, ..., output = FALSE)$text.tidy 5 | } 6 | 7 | assert( 8 | 'tidy_source() tries to keep R comments', 9 | identical(tidy.res('1+1#asdf'), '1 + 1 #asdf'), 10 | identical(tidy.res('paste(1 #asdf\n,2)'), 'paste(1 #asdf\n, 2)'), 11 | identical(tidy.res(c('# asdf', '1+1')), c('# asdf', '1 + 1')) 12 | ) 13 | 14 | assert( 15 | 'tidy_source() preserves backslashes in comments', 16 | identical(tidy.res('# \\a \\b \\c'), '# \\a \\b \\c') 17 | ) 18 | 19 | assert( 20 | 'tidy_source() can preserve blank lines among non-empty code lines', 21 | identical(tidy.res(c('if(TRUE){1+1', '', '}', '', '# a comment')), 22 | c('if (TRUE) {\n 1 + 1\n \n}', '', '# a comment')) 23 | ) 24 | 25 | x1 = paste(c('#', letters), collapse = ' ') 26 | 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') 27 | assert( 28 | 'long comments are wrapped in tidy_source()', 29 | identical(tidy.res(x1, width.cutoff = 20), x2), 30 | identical( 31 | tidy.res(rep(x1, 2), width.cutoff = 20), 32 | 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 a b c d', 33 | '# e f g h i j k l m n', '# o p q r s t u v w x', '# y z') 34 | ), 35 | identical(tidy.res(c(x1, '1+1', x1), width.cutoff = 20), c(x2, '1 + 1', x2)) 36 | ) 37 | assert( 38 | 'roxygen comments are not wrapped', 39 | identical(tidy.res(c(paste("#'", x1), '1*1')), c(paste("#'", x1), '1 * 1')) 40 | ) 41 | 42 | x1 = ' 43 | # only a comment 44 | ' 45 | x2 = c('', '# only a comment', '', '') 46 | assert( 47 | 'tidy_source() can deal with code that only contains a comment', 48 | identical(tidy.res(x1), c('', '# only a comment', '')), 49 | identical(tidy.res(x2), x2) 50 | ) 51 | 52 | x1 = '{if (TRUE) { 53 | 1 54 | } 55 | else 2}' 56 | assert( 57 | 'tidy_source() moves else back if it is in a standalone line', 58 | identical(tidy.res(x1), '{\n if (TRUE) {\n 1\n } else 2\n}') 59 | ) 60 | 61 | x1 = '{x=1 62 | else.x=2 63 | }' 64 | 65 | assert( 66 | 'should not move any lines starting with `else` back to the previous line', 67 | tidy.res(x1) %==% '{\n x = 1\n else.x = 2\n}' 68 | ) 69 | 70 | x1 = 'if (TRUE) {# comment 71 | 1 72 | }' 73 | assert( 74 | 'comments after { are moved down one line', 75 | identical(tidy.res(x1), 'if (TRUE) {\n # comment\n 1\n}') 76 | ) 77 | 78 | assert( 79 | 'empty code returns empty string', 80 | identical(tidy.res(''), ''), 81 | identical(tidy.res(c('', ' ')), c('', ' ')) 82 | ) 83 | 84 | assert( 85 | 'keep.comment=FALSE removes comments', 86 | identical(tidy.res(c('# a comment', '1+1'), comment = FALSE), '1 + 1') 87 | ) 88 | 89 | if (packageVersion('formatR') > '0.8') assert( 90 | 'when comment=FALSE and everything is comment, tidy_source() returns character(0)', 91 | identical(tidy.res('# a comment', comment = FALSE), character(0)) 92 | ) 93 | 94 | x1 = '1+1 95 | 96 | if(F){ 97 | 98 | } 99 | ' 100 | assert( 101 | 'keep.blank.line=FALSE removes blank lines', 102 | identical(tidy.res(x1), c('1 + 1', '', 'if (F) {\n \n}', '')), 103 | identical(tidy.res(x1, blank = FALSE), c('1 + 1', 'if (F) {\n}')) 104 | ) 105 | 106 | assert( 107 | '= can be replaced with <- when replace.assign=TRUE', 108 | identical(tidy.res('x=1;c(x=1)', arrow = TRUE), c('x <- 1', 'c(x = 1)')) 109 | ) 110 | 111 | assert( 112 | 'since R 3.0.0 comments can be written with double quotes in them', 113 | identical(tidy.res('1+1# hello "world"'), "1 + 1 # hello 'world'") 114 | ) 115 | 116 | x1 = 'x=" 117 | # this is not a comment 118 | "' 119 | assert( 120 | 'since R 3.0.0, # in the beginning of a line does not necessarily mean comments', 121 | identical(tidy.res(x1), 'x = "\n# this is not a comment\n"') 122 | ) 123 | 124 | assert( 125 | 'the shebang is preserved', 126 | identical(tidy.res(c('#!/usr/bin/Rscript', '1+1')), c('#!/usr/bin/Rscript', '1 + 1')) 127 | ) 128 | 129 | x1 = paste0('x="', r <- rand_string(2000), '"') 130 | assert( 131 | 'Long strings (> 1000 chars) can be preserved', 132 | tidy.res(x1) %==% paste0('x = "', r, '"') 133 | ) 134 | 135 | x1 = 'x = " 136 | this is a 137 | character string 138 | "' 139 | assert( 140 | 'line breaks in strings are preserved instead of being replaced by \\n', 141 | tidy.res(x1) %==% x1 142 | ) 143 | -------------------------------------------------------------------------------- /man/tidy_source.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tidy.R 3 | \name{tidy_source} 4 | \alias{tidy_source} 5 | \title{Reformat R code while preserving blank lines and comments} 6 | \usage{ 7 | tidy_source(source = "clipboard", comment = getOption("formatR.comment", 8 | TRUE), blank = getOption("formatR.blank", TRUE), arrow = getOption("formatR.arrow", 9 | FALSE), brace.newline = getOption("formatR.brace.newline", FALSE), 10 | indent = getOption("formatR.indent", 4), output = TRUE, text = NULL, 11 | width.cutoff = getOption("width"), ...) 12 | } 13 | \arguments{ 14 | \item{source}{a character string: location of the source code (default to be 15 | the clipboard; this means we can copy the code to clipboard and use 16 | \code{tidy_source()} without specifying the argument \code{source})} 17 | 18 | \item{comment}{whether to keep comments (\code{TRUE} by default)} 19 | 20 | \item{blank}{whether to keep blank lines (\code{TRUE} by default)} 21 | 22 | \item{arrow}{whether to replace the assign operator \code{=} with \code{<-}} 23 | 24 | \item{brace.newline}{whether to put the left brace \code{\{} to a new line 25 | (default \code{FALSE})} 26 | 27 | \item{indent}{number of spaces to indent the code (default 4)} 28 | 29 | \item{output}{output to the console or a file using \code{\link{cat}}?} 30 | 31 | \item{text}{an alternative way to specify the input: if it is \code{NULL}, 32 | the function will read the source code from the \code{source} argument; 33 | alternatively, if \code{text} is a character vector containing the source 34 | code, it will be used as the input and the \code{source} argument will be 35 | ignored} 36 | 37 | \item{width.cutoff}{passed to \code{\link{deparse}}: integer in [20, 500] 38 | determining the cutoff at which line-breaking is tried (default to be 39 | \code{getOption("width")})} 40 | 41 | \item{...}{other arguments passed to \code{\link{cat}}, e.g. \code{file} 42 | (this can be useful for batch-processing R scripts, e.g. 43 | \code{tidy_source(source = 'input.R', file = 'output.R')})} 44 | } 45 | \value{ 46 | A list with components \item{text.tidy}{the reformatted code as a 47 | character vector} \item{text.mask}{the code containing comments, which are 48 | masked in assignments or with the weird operator} 49 | } 50 | \description{ 51 | This function returns reformatted source code; it tries to preserve blank 52 | lines and comments, which is different with \code{\link{parse}} and 53 | \code{\link{deparse}}. It can also replace \code{=} with \code{<-} where 54 | \code{=} means assignments, and reindent code by a specified number of spaces 55 | (default is 4). 56 | } 57 | \note{ 58 | Be sure to read the reference to know other limitations. 59 | } 60 | \examples{ 61 | library(formatR) 62 | 63 | ## a messy R script 64 | messy = system.file("format", "messy.R", package = "formatR") 65 | tidy_source(messy) 66 | 67 | ## use the 'text' argument 68 | src = readLines(messy) 69 | 70 | ## source code 71 | cat(src, sep = "\\n") 72 | 73 | ## the formatted version 74 | tidy_source(text = src) 75 | 76 | ## preserve blank lines 77 | tidy_source(text = src, blank = TRUE) 78 | 79 | ## indent with 2 spaces 80 | tidy_source(text = src, indent = 2) 81 | 82 | ## discard comments! 83 | tidy_source(text = src, comment = FALSE) 84 | 85 | ## wanna see the gory truth?? 86 | tidy_source(text = src, output = FALSE)$text.mask 87 | 88 | 89 | ## tidy up the source code of image demo 90 | x = file.path(system.file(package = "graphics"), "demo", "image.R") 91 | 92 | # to console 93 | tidy_source(x) 94 | 95 | # to a file 96 | f = tempfile() 97 | tidy_source(x, blank = TRUE, file = f) 98 | 99 | ## check the original code here and see the difference 100 | file.show(x) 101 | file.show(f) 102 | 103 | ## use global options 104 | options(comment = TRUE, blank = FALSE) 105 | tidy_source(x) 106 | 107 | ## if you've copied R code into the clipboard 108 | if (interactive()) { 109 | tidy_source("clipboard") 110 | ## write into clipboard again 111 | tidy_source("clipboard", file = "clipboard") 112 | } 113 | 114 | ## the if-else structure 115 | tidy_source(text = c("{if(TRUE)1 else 2; if(FALSE){1+1", "## comments", "} else 2}")) 116 | } 117 | \references{ 118 | \url{http://yihui.name/formatR} (an introduction to this package, 119 | with examples and further notes) 120 | } 121 | \seealso{ 122 | \code{\link{parse}}, \code{\link{deparse}} 123 | } 124 | \author{ 125 | Yihui Xie <\url{http://yihui.name}> with substantial contribution 126 | from Yixuan Qiu <\url{http://yixuan.cos.name}> 127 | } 128 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | # replace `=` by `<-` in expressions 2 | replace_assignment = function(exp) { 3 | wc = codetools::makeCodeWalker( 4 | call = function(e, w) { 5 | cl = codetools::walkCode(e[[1]], w) 6 | arg = lapply(as.list(e[-1]), function(a) if (missing(a)) NA else { 7 | codetools::walkCode(a, w) 8 | }) 9 | as.call(c(list(cl), arg)) 10 | }, 11 | leaf = function(e, w) { 12 | if (length(e) == 0 || inherits(e, "srcref")) return(NULL) 13 | # x = 1 is actually `=`(x, 1), i.e. `=` is a function 14 | if (identical(e, as.name("="))) e <- as.name("<-") 15 | e 16 | }) 17 | lapply(as.list(exp), codetools::walkCode, w = wc) 18 | } 19 | 20 | ## mask comments to cheat R 21 | mask_comments = function(x, width, keep.blank.line) { 22 | d = utils::getParseData(parse_source(x)) 23 | if (nrow(d) == 0 || (n <- sum(d$terminal)) == 0) return(x) 24 | d = d[d$terminal, ] 25 | d = fix_parse_data(d, x) 26 | d.line = d$line1; d.line2 = d$line2; d.token = d$token; d.text = d$text 27 | 28 | # move else back 29 | for (i in which(d.token == 'ELSE')) { 30 | delta = d.line[i] - d.line[i - 1] 31 | d.line[i:n] = d.line[i:n] - delta 32 | d.line2[i:n] = d.line2[i:n] - delta 33 | } 34 | # how many blank lines after each token? 35 | blank = c(pmax(d.line[-1] - d.line2[-n] - 1, 0), 0) 36 | 37 | i = d.token == 'COMMENT' 38 | # double backslashes and replace " with ' in comments 39 | d.text[i] = gsub('"', "'", gsub('\\\\', '\\\\\\\\', d.text[i])) 40 | 41 | c0 = d.line[-1] != d.line[-n] # is there a line change? 42 | c1 = i & c(TRUE, c0 | (d.token[-n] == "'{'")) # must be comment blocks 43 | c2 = i & !c1 # inline comments 44 | c3 = c1 & grepl("^#+[-'+]", d.text) # roxygen or knitr spin() comments 45 | if (grepl('^#!', d.text[1])) c3[1] = TRUE # shebang comment 46 | 47 | # reflow blocks of comments: first collapse them, then wrap them 48 | i1 = which(c1 & !c3) # do not wrap roxygen comments 49 | j1 = i1[1] 50 | if (length(i1) > 1) for (i in 2:length(i1)) { 51 | # two neighbor lines of comments 52 | if (d.line[i1[i]] - d.line[i1[i - 1]] == 1) { 53 | j2 = i1[i] 54 | d.text[j1] = paste(d.text[j1], sub('^#+', '', d.text[j2])) 55 | d.text[j2] = '' 56 | c1[j2] = FALSE # the second line is no longer a comment 57 | } else j1 = i1[i] 58 | } 59 | 60 | # mask block and inline comments 61 | d.text[c1 & !c3] = reflow_comments(d.text[c1 & !c3], width) 62 | d.text[c3] = sprintf('invisible("%s%s%s")', begin.comment, d.text[c3], end.comment) 63 | d.text[c2] = sprintf('%%InLiNe_IdEnTiFiEr%% "%s"', d.text[c2]) 64 | 65 | # add blank lines 66 | if (keep.blank.line) for (i in seq_along(d.text)) { 67 | if (blank[i] > 0) 68 | d.text[i] = paste(c(d.text[i], rep(blank.comment, blank[i])), collapse = '\n') 69 | } 70 | 71 | unlist(lapply(split(d.text, d.line), paste, collapse = ' '), use.names = FALSE) 72 | } 73 | 74 | # no blank lines before an 'else' statement! 75 | move_else = function(x) { 76 | blank = grepl('^\\s*$', x) 77 | if (!any(blank)) return(x) 78 | else.line = grep('^\\s*else(\\s+|$)', x) 79 | for (i in else.line) { 80 | j = i - 1 81 | while (blank[j]) { 82 | blank[j] = FALSE; j = j - 1 # search backwards & rm blank lines 83 | warning('removed blank line ', j, " (should not put an 'else' in a separate line!)") 84 | } 85 | } 86 | x[blank] = blank.comment 87 | x 88 | } 89 | 90 | # a literal # must be writen in double quotes, e.g. "# is not comment" 91 | mask_inline = function(x) { 92 | # move comments after { to the next line 93 | if (length(idx <- grep('\\{\\s*#.*$', x))) { 94 | p = paste('{\ninvisible("', begin.comment, '\\1', end.comment, '")', sep = '') 95 | x[idx] = gsub('\\{\\s*(#.*)$', p, x[idx]) 96 | } 97 | gsub('(#[^"]*)$', ' %InLiNe_IdEnTiFiEr% "\\1"', x) 98 | } 99 | 100 | # reflow comments (excluding roxygen comments) 101 | reflow_comments = function(x, width) { 102 | if (length(x) == 0) return(x) 103 | # returns a character vector of the same length as x 104 | b = sub('^(#+).*', '\\1', x) 105 | mapply(function(res, prefix) { 106 | paste(sprintf( 107 | 'invisible("%s%s%s")', begin.comment, paste(prefix, res), end.comment 108 | ), collapse = '\n') 109 | }, strwrap(sub('^#+', '', x), width = width, simplify = FALSE), b) 110 | } 111 | 112 | # reindent lines with a different number of spaces 113 | reindent_lines = function(text, n = 2) { 114 | if (length(text) == 0) return(text) 115 | if (n == 4) return(text) # no need to do anything 116 | s = paste(rep(' ', n), collapse = '') 117 | unlist(lapply(strsplit(text, '\n'), function(x) { 118 | t1 = gsub('^( *)(.*)', '\\1', x) 119 | t2 = gsub('^( *)(.*)', '\\2', x) 120 | paste(gsub(' {4}', s, t1), t2, sep = '', collapse = '\n') 121 | }), use.names = FALSE) 122 | } 123 | 124 | # move { to the next line 125 | move_leftbrace = function(text) { 126 | if (!length(text)) return(text) 127 | # the reason to use lapply() here is that text is a vector of source code with 128 | # each element being a complete R expression; we do not want to break the 129 | # expression structure; same reason for reindent_lines() above 130 | unlist(lapply(strsplit(text, '\n'), function(x) { 131 | if (length(x) > 1L && length(idx <- grep('(\\)|else) \\{$', x))) { 132 | # indent the same amount of spaces as the { lines 133 | pre = gsub('^( *)(.*)', '\\1', x[idx]) 134 | x[idx] = mapply(gsub, '(\\)|else) \\{$', sprintf('\\1\n%s{', pre), x[idx], 135 | USE.NAMES = FALSE) 136 | } 137 | paste(x, collapse = '\n') 138 | }), use.names = FALSE) 139 | } 140 | 141 | # parse but do not keep source (moved from knitr) 142 | parse_only = function(code) { 143 | if (length(code) == 0) return(expression()) 144 | base::parse(text = code, keep.source = FALSE) 145 | } 146 | 147 | # copied from highr 148 | # TODO: eventually remove the hack for R <= 3.2.2 149 | parse_source = if (getRversion() > '3.2.2') function(lines) { 150 | parse(text = lines, keep.source = TRUE) 151 | } else function(lines) { 152 | # adapted from evaluate 153 | src = srcfilecopy('', lines = '') 154 | if (length(grep('\n', lines))) lines = unlist(strsplit( 155 | sub('$', '\n', as.character(lines)), '\n' 156 | )) 157 | src$lines = lines 158 | parse(text = lines, srcfile = src) 159 | } 160 | 161 | # restore backslashes 162 | restore_bs = function(x) gsub('\\\\\\\\', '\\\\', x) 163 | 164 | # a workaround for the R bug (long strings are truncated in getParseData()): 165 | # https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=16354 166 | fix_parse_data = function(d, x) { 167 | if (length(s <- which(d$token == 'STR_CONST')) == 0) return(d) 168 | ws = s[grep('^\\[\\d+ (wide )?chars quoted with \'"\'\\]$', d$text[s])] 169 | for (i in ws) { 170 | di = d[i, , drop = FALSE] 171 | d[i, 'text'] = get_src_string(x, di$line1, di$line2, di$col1, di$col2) 172 | } 173 | d[s, 'text'] = mask_line_break(d[s, 'text']) 174 | d 175 | } 176 | 177 | get_src_string = function(x, l1, l2, c1, c2) { 178 | if (l1 == l2) return(substr(x[l1], c1, c2)) 179 | x[l1] = substr(x[l1], c1, nchar(x[l1])) 180 | x[l2] = substr(x[l2], 1, c2) 181 | paste(x[l1:l2], collapse = '\n') 182 | } 183 | 184 | # generate a random string 185 | CHARS = c(letters, LETTERS, 0:9) 186 | rand_string = function(len = 32) { 187 | paste(sample(CHARS, len, replace = TRUE), collapse = '') 188 | } 189 | 190 | .env = new.env() 191 | .env$line_break = NULL 192 | 193 | mask_line_break = function(x) { 194 | if (length(grep('\n', x)) == 0) return(x) 195 | m = (function() { 196 | for (i in 2:10) { 197 | for (j in 1:100) if (length(grep(s <- rand_string(i), x)) == 0) return(s) 198 | } 199 | })() 200 | if (is.null(m)) return(x) 201 | .env$line_break = m 202 | gsub('\n', m, x) 203 | } 204 | -------------------------------------------------------------------------------- /R/tidy.R: -------------------------------------------------------------------------------- 1 | #' Reformat R code while preserving blank lines and comments 2 | #' 3 | #' This function returns reformatted source code; it tries to preserve blank 4 | #' lines and comments, which is different with \code{\link{parse}} and 5 | #' \code{\link{deparse}}. It can also replace \code{=} with \code{<-} where 6 | #' \code{=} means assignments, and reindent code by a specified number of spaces 7 | #' (default is 4). 8 | #' @param source a character string: location of the source code (default to be 9 | #' the clipboard; this means we can copy the code to clipboard and use 10 | #' \code{tidy_source()} without specifying the argument \code{source}) 11 | #' @param comment whether to keep comments (\code{TRUE} by default) 12 | #' @param blank whether to keep blank lines (\code{TRUE} by default) 13 | #' @param arrow whether to replace the assign operator \code{=} with \code{<-} 14 | #' @param brace.newline whether to put the left brace \code{\{} to a new line 15 | #' (default \code{FALSE}) 16 | #' @param indent number of spaces to indent the code (default 4) 17 | #' @param output output to the console or a file using \code{\link{cat}}? 18 | #' @param text an alternative way to specify the input: if it is \code{NULL}, 19 | #' the function will read the source code from the \code{source} argument; 20 | #' alternatively, if \code{text} is a character vector containing the source 21 | #' code, it will be used as the input and the \code{source} argument will be 22 | #' ignored 23 | #' @param width.cutoff passed to \code{\link{deparse}}: integer in [20, 500] 24 | #' determining the cutoff at which line-breaking is tried (default to be 25 | #' \code{getOption("width")}) 26 | #' @param ... other arguments passed to \code{\link{cat}}, e.g. \code{file} 27 | #' (this can be useful for batch-processing R scripts, e.g. 28 | #' \code{tidy_source(source = 'input.R', file = 'output.R')}) 29 | #' @return A list with components \item{text.tidy}{the reformatted code as a 30 | #' character vector} \item{text.mask}{the code containing comments, which are 31 | #' masked in assignments or with the weird operator} 32 | #' @note Be sure to read the reference to know other limitations. 33 | #' @author Yihui Xie <\url{http://yihui.name}> with substantial contribution 34 | #' from Yixuan Qiu <\url{http://yixuan.cos.name}> 35 | #' @seealso \code{\link{parse}}, \code{\link{deparse}} 36 | #' @references \url{http://yihui.name/formatR} (an introduction to this package, 37 | #' with examples and further notes) 38 | #' @import utils 39 | #' @export 40 | #' @example inst/examples/tidy.source.R 41 | tidy_source = function( 42 | source = 'clipboard', comment = getOption('formatR.comment', TRUE), 43 | blank = getOption('formatR.blank', TRUE), 44 | arrow = getOption('formatR.arrow', FALSE), 45 | brace.newline = getOption('formatR.brace.newline', FALSE), 46 | indent = getOption('formatR.indent', 4), 47 | output = TRUE, text = NULL, 48 | width.cutoff = getOption('width'), ... 49 | ) { 50 | if (is.null(text)) { 51 | if (source == 'clipboard' && Sys.info()['sysname'] == 'Darwin') { 52 | source = pipe('pbpaste') 53 | } 54 | } else { 55 | source = textConnection(text); on.exit(close(source), add = TRUE) 56 | } 57 | text = readLines(source, warn = FALSE) 58 | if (length(text) == 0L || all(grepl('^\\s*$', text))) { 59 | if (output) cat('\n', ...) 60 | return(list(text.tidy = text, text.mask = text)) 61 | } 62 | if (blank) { 63 | one = paste(text, collapse = '\n') # record how many line breaks before/after 64 | n1 = attr(regexpr('^\n*', one), 'match.length') 65 | n2 = attr(regexpr('\n*$', one), 'match.length') 66 | } 67 | on.exit(.env$line_break <- NULL, add = TRUE) 68 | if (comment) text = mask_comments(text, width.cutoff, blank) 69 | text.mask = tidy_block(text, width.cutoff, arrow && length(grep('=', text))) 70 | text.tidy = if (comment) unmask_source(text.mask) else text.mask 71 | text.tidy = reindent_lines(text.tidy, indent) 72 | if (brace.newline) text.tidy = move_leftbrace(text.tidy) 73 | # restore new lines in the beginning and end 74 | if (blank) text.tidy = c(rep('', n1), text.tidy, rep('', n2)) 75 | if (output) cat(text.tidy, sep = '\n', ...) 76 | invisible(list(text.tidy = text.tidy, text.mask = text.mask)) 77 | } 78 | 79 | ## if you have variable names like this in your code, then you really beat me... 80 | begin.comment = '.BeGiN_TiDy_IdEnTiFiEr_HaHaHa' 81 | end.comment = '.HaHaHa_EnD_TiDy_IdEnTiFiEr' 82 | pat.comment = sprintf('invisible\\("\\%s|\\%s"\\)', begin.comment, end.comment) 83 | mat.comment = sprintf('invisible\\("\\%s([^"]*)\\%s"\\)', begin.comment, end.comment) 84 | inline.comment = ' %InLiNe_IdEnTiFiEr%[ ]*"([ ]*#[^"]*)"' 85 | blank.comment = sprintf('invisible("%s%s")', begin.comment, end.comment) 86 | 87 | # wrapper around parse() and deparse() 88 | tidy_block = function(text, width = getOption('width'), arrow = FALSE) { 89 | exprs = parse_only(text) 90 | if (length(exprs) == 0) return(character(0)) 91 | exprs = if (arrow) replace_assignment(exprs) else as.list(exprs) 92 | sapply(exprs, function(e) paste(base::deparse(e, width), collapse = '\n')) 93 | } 94 | 95 | # Restore the real source code from the masked text 96 | unmask_source = function(text.mask) { 97 | if (length(text.mask) == 0) return(text.mask) 98 | m = .env$line_break 99 | if (!is.null(m)) text.mask = gsub(m, '\n', text.mask) 100 | ## if the comments were separated into the next line, then remove '\n' after 101 | ## the identifier first to move the comments back to the same line 102 | text.mask = gsub('%InLiNe_IdEnTiFiEr%[ ]*\n', '%InLiNe_IdEnTiFiEr%', text.mask) 103 | ## move 'else ...' back to the last line 104 | text.mask = gsub('\n\\s*else(\\s+|$)', ' else\\1', text.mask) 105 | if (any(grepl('\\\\\\\\', text.mask)) && 106 | (any(grepl(mat.comment, text.mask)) || any(grepl(inline.comment, text.mask)))) { 107 | m = gregexpr(mat.comment, text.mask) 108 | regmatches(text.mask, m) = lapply(regmatches(text.mask, m), restore_bs) 109 | m = gregexpr(inline.comment, text.mask) 110 | regmatches(text.mask, m) = lapply(regmatches(text.mask, m), restore_bs) 111 | } 112 | text.tidy = gsub(pat.comment, '', text.mask) 113 | # inline comments should be terminated by $ or \n 114 | text.tidy = gsub(paste(inline.comment, '(\n|$)', sep = ''), ' \\1\\2', text.tidy) 115 | # the rest of inline comments should be appended by \n 116 | gsub(inline.comment, ' \\1\n', text.tidy) 117 | } 118 | 119 | 120 | #' Format all R scripts under a directory, or specified R scripts 121 | #' 122 | #' \code{tidy_dir()} first looks for all the R scripts under a directory (using 123 | #' the pattern \code{"[.][RrSsQq]$"}), then uses \code{\link{tidy_source}} to 124 | #' tidy these scripts. The original scripts will be overwritten with reformatted 125 | #' code if reformatting was successful. You may need to back up the original 126 | #' directory first if you do not fully understand the tricks used by 127 | #' \code{\link{tidy_source}}. \code{tidy_file()} formats specified R scripts. 128 | #' @param path the directory 129 | #' @param recursive whether to recursively look for R scripts under \code{path} 130 | #' @param ... other arguments to be passed to \code{\link{tidy_source}} 131 | #' @param file a vector of filenames 132 | #' @return Invisible \code{NULL}. 133 | #' @author Yihui Xie (\code{tidy_dir}) and Ed Lee (\code{tidy_file}) 134 | #' @seealso \code{\link{tidy_source}} 135 | #' @export 136 | #' @examples 137 | #' library(formatR) 138 | #' 139 | #' path = tempdir() 140 | #' file.copy(system.file('demo', package = 'base'), path, recursive=TRUE) 141 | #' tidy_dir(path, recursive=TRUE) 142 | tidy_dir = function(path = '.', recursive = FALSE, ...) { 143 | tidy_file(list.files( 144 | path, pattern = '[.][RrSsQq]$', full.names = TRUE, recursive = recursive 145 | ), ...) 146 | } 147 | 148 | #' @export 149 | #' @rdname tidy_dir 150 | tidy_file = function(file, ...) { 151 | for (f in file) { 152 | message("tidying ", f) 153 | try(tidy_source(f, file = f, ...)) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /vignettes/formatR.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: formatR 3 | subtitle: Format R code automatically 4 | author: Yihui Xie 5 | date: "`r Sys.Date()`" 6 | show_toc: true 7 | slug: formatR 8 | githubEditURL: https://github.com/yihui/formatR/edit/master/vignettes/formatR.Rmd 9 | output: 10 | knitr:::html_vignette: 11 | toc: yes 12 | vignette: > 13 | %\VignetteEngine{knitr::rmarkdown} 14 | %\VignetteIndexEntry{An Introduction to formatR} 15 | --- 16 | 17 | 22 | 23 | ```{r setup, include=FALSE} 24 | options(formatR.indent = 4, width = 70) 25 | knitr::opts_chunk$set(tidy = TRUE) 26 | ``` 27 | 28 | # 1. Installation 29 | 30 | You can install **formatR** from [CRAN](https://cran.r-project.org/package=formatR), or [XRAN](http://yihui.name/xran) if you want to test the latest development version: 31 | 32 | ```{r eval=FALSE} 33 | install.packages('formatR', repos = 'http://cran.rstudio.com') 34 | #' to install the development version, run 35 | #' install.packages('formatR', repos = 'http://yihui.name/xran') 36 | ``` 37 | 38 | 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. 39 | 40 | ```{r} 41 | library(formatR) 42 | sessionInfo() 43 | ``` 44 | 45 | # 2. Reformat R code 46 | 47 | The **formatR** package was designed to reformat R code to improve readability; the main workhorse is the function `tidy_source()`. Features include: 48 | 49 | - long lines of code and comments are reorganized into appropriately shorter ones 50 | - spaces and indent are added where necessary 51 | - comments are preserved in most cases 52 | - the number of spaces to indent the code (i.e. tab width) can be specified (default is 4) 53 | - an `else` statement in a separate line without the leading `}` will be moved one line back 54 | - `=` as an assignment operator can be replaced with `<-` 55 | - the left brace `{` can be moved to a new line 56 | 57 | Below is an example of what `tidy_source()` can do. The source code is: 58 | 59 | ```{r example, eval=FALSE, tidy=FALSE} 60 | ## comments are retained; 61 | # a comment block will be reflowed if it contains long comments; 62 | #' roxygen comments will not be wrapped in any case 63 | 1+1 64 | 65 | if(TRUE){ 66 | x=1 # inline comments 67 | }else{ 68 | x=2;print('Oh no... ask the right bracket to go away!')} 69 | 1*3 # one space before this comment will become two! 70 | 2+2+2 # only 'single quotes' are allowed in comments 71 | 72 | lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100))) ### a linear model 73 | 1+1+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 74 | ## here is a long long long long long long long long long long long long long comment which will be wrapped 75 | ``` 76 | 77 | We can copy the above code to clipboard, and type `tidy_source(width.cutoff = 50)` to get: 78 | 79 | ```{r example, eval=FALSE, tidy.opts=list(width.cutoff=50)} 80 | ``` 81 | 82 | Two applications of `tidy_source()`: 83 | 84 | - `tidy_dir()` can reformat all R scripts under a directory 85 | - `usage()` can reformat the usage of a function, e.g. compare `usage()` with the default output of `args()`: 86 | ```{r collapse=TRUE} 87 | library(formatR) 88 | usage(glm, width = 40) # can set arbitrary width here 89 | args(glm) 90 | ``` 91 | 92 | # 3. The Graphical User Interface 93 | 94 | 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/)): 95 | 96 | ```r 97 | formatR::tidy_app() 98 | ``` 99 | 100 | [![R source code before tidying](https://db.yihui.name/imgur/lUgtEAb.png)](https://yihui.shinyapps.io/formatR/) 101 | 102 | After hitting the `Tidy` button: 103 | 104 | [![R source code after tidying](https://db.yihui.name/imgur/TBZm0B8.png)](https://yihui.shinyapps.io/formatR/) 105 | 106 | # 4. Evaluate the code and mask output in comments 107 | 108 | 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: 109 | 110 | ```{r comment=NA} 111 | set.seed(123) 112 | tidy_eval(text = c("a<-1+1;a # print the value", "matrix(rnorm(10),5)")) 113 | ``` 114 | 115 | 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: 116 | 117 | ```{r eval=FALSE} 118 | library(formatR) 119 | tidy_eval() 120 | # without specifying any arguments, it reads code from clipboard 121 | ``` 122 | 123 | # 5. Showcase 124 | 125 | We continue the example code in Section 2, using different arguments in `tidy_source()` such as `arrow`, `blank`, `indent`, `brace.newline` and `comment`, etc. 126 | 127 | ## Replace `=` with `<-` 128 | 129 | ```{r example, eval=FALSE, echo=6, tidy.opts=list(arrow=TRUE)} 130 | ``` 131 | 132 | ## Discard blank lines 133 | 134 | Note the 5th line (an empty line) was discarded: 135 | 136 | ```{r example, eval=FALSE, echo=1:6, tidy.opts=list(blank = FALSE)} 137 | ``` 138 | 139 | ## Reindent code (2 spaces instead of 4) 140 | 141 | ```{r example, eval=FALSE, echo=6, tidy.opts=list(indent = 2)} 142 | ``` 143 | 144 | ## Move left braces `{` to new lines 145 | 146 | ```{r example, eval=FALSE, echo=6, tidy.opts=list(brace.newline = TRUE)} 147 | ``` 148 | 149 | ## Discard comments 150 | 151 | ```{r example, eval=FALSE, tidy.opts=list(comment = FALSE, width.cutoff = 50)} 152 | ``` 153 | 154 | # 6. Further notes 155 | 156 | 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 issues that `tidy_source()` may fail. 157 | 158 | ## In-line comments after an incomplete expression or ; 159 | 160 | ```r 161 | 1 + 2 + ## comments after an incomplete line 162 | 3 + 4 163 | x <- ## this is not a complete expression 164 | 5 165 | x <- 1; # you should not use ; here! 166 | ``` 167 | 168 | It is not a good idea to interrupt R code with comments and sometimes it can be confusing -- comments should come after a complete R expression naturally; by the way, `tidy_source()` will move the comments after `{` to the next line, e.g. 169 | 170 | ```{r comment-brace, tidy=FALSE, eval=FALSE} 171 | if (TRUE) {## comments 172 | } 173 | ``` 174 | 175 | will become 176 | 177 | ```{r comment-brace, eval=FALSE} 178 | ``` 179 | 180 | ## Inappropriate blank lines 181 | 182 | 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. 183 | 184 | ```r 185 | if (TRUE) 186 | 187 | {'this is a BAD style of R programming!'} else 'failure!' 188 | ``` 189 | 190 | There should not be a blank line after the `if` statement. Of course `blank = FALSE` will not fail in this case. 191 | 192 | ## `?` with comments 193 | 194 | 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. 195 | 196 | ```r 197 | ?sd # help on sd() 198 | ``` 199 | 200 | In this case, it is recommended to use the function `help()` instead of the short-hand version `?`. 201 | 202 | ## `->` with comments 203 | 204 | We can also use the right arrow `->` for assignment, e.g. `1:10 -> x`. I believe this flexibility is worthless, and it is amazing that a language has three assignment operators: `<-`, `=` and `->` (whereas almost all other languages uses `=` for assignment). Bad news for **formatR** is that it is unable to format code using both `->` and comments in a line, e.g. 205 | 206 | ```r 207 | 1:10 -> x # assignment with right arrow 208 | ``` 209 | 210 | I recommend you to use `<-` or `=` consistently. What is more important is consistency. I always use `=` because it causes me no confusion (I do not believe it is ever possible for people to interpret `fun(a = 1)` as assigning `1` to a variable `a` instead of passing an argument value) and `<-` is more dangerous because it works everywhere (you might have unconsciously created a new variable `a` in `fun(a <- 1)`; see [an example here](https://stat.ethz.ch/pipermail/r-devel/2011-December/062786.html)). The only disadvantage is that most R people use `<-` so it may be difficult to collaborate with other people. 211 | 212 | # 7. How does `tidy_source()` actually work? 213 | 214 | 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: 215 | 216 | ```r 217 | # asdf 218 | ``` 219 | 220 | It will be first masked as 221 | 222 | ```r 223 | invisible(".IDENTIFIER1 # asdf.IDENTIFIER2") 224 | ``` 225 | 226 | 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. 227 | 228 | Inline comments are handled differently: two spaces will be added before the hash symbol `#`, e.g. 229 | 230 | ```r 231 | 1+1# comments 232 | ``` 233 | 234 | will become 235 | 236 | ```r 237 | 1+1 # comments 238 | ``` 239 | 240 | Inline comments are first disguised as a weird operation with its preceding R code, which is essentially meaningless but syntactically correct! For example, 241 | 242 | ```r 243 | 1+1 %InLiNe_IdEnTiFiEr% "# comments" 244 | ``` 245 | 246 | 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 `%InLiNe_IdEnTiFiEr%` and surrounding double quotes). 247 | 248 | 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. 249 | 250 | # 8. Global options 251 | 252 | There are global options which can override some arguments in `tidy_source()`: 253 | 254 | | argument | global option | default | 255 | |-----------------|------------------------------------|---------| 256 | | `comment` | `options('formatR.comment')` | `TRUE` | 257 | | `blank` | `options('formatR.blank')` | `TRUE` | 258 | | `arrow` | `options('formatR.arrow')` | `FALSE` | 259 | | `indent` | `options('formatR.indent')` | `4` | 260 | | `brace.newline` | `options('formatR.brace.newline')` | `FALSE` | 261 | 262 | Also note that single lines of long comments will be wrapped into shorter ones automatically, but roxygen comments will not be wrapped (i.e., comments that begin with `#'`). 263 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | CHANGES IN formatR VERSION 1.5 (unreleased) 2 | 3 | NEW FEATURES 4 | 5 | o added a new function `tidy_file()` to format specified R scripts (thanks, 6 | @edlee123, #61) 7 | 8 | CHANGES IN formatR VERSION 1.4 9 | 10 | NEW FEATURES 11 | 12 | o `tidy_source()` can preserve line breaks in character strings in source code 13 | 14 | MAJOR CHANGES 15 | 16 | o the deprecated functions tidy.source(), tidy.dir(), and tidy.eval() have 17 | been removed; use tidy_source(), tidy_dir() and tidy_eval() instead 18 | 19 | o comments that begin with `#+` or `#-` are no longer wrapped; such comments 20 | are treated as knitr chunk options in `knitr::spin()` (#52) 21 | 22 | BUG FIXES 23 | 24 | o `tidy_source()` should not write an extra space to the last line of code 25 | (thanks, @mr-karan, #49) 26 | 27 | o long strings (> 1000 characters) in source code can be preserved now 28 | (thanks, @jholtman, #50) 29 | 30 | o `tidy_source()` might move any lines of code starting with `else` back to 31 | the previous lines (thanks, @Auburngrads, #51) 32 | 33 | CHANGES IN formatR VERSION 1.3 34 | 35 | NEW FEATURES 36 | 37 | o `tidy_source()` can deal with multibyte characters that cannot represented 38 | in the system native encoding now (on Windows) 39 | 40 | o `usage()` works for functions obtained from `::` or `:::` now, e.g. 41 | `usage(formatR::tidy_source)` 42 | 43 | CHANGES IN formatR VERSION 1.2 44 | 45 | MAJOR CHANGES 46 | 47 | o the minimal required R version is 3.0.2 now 48 | 49 | CHANGES IN formatR VERSION 1.1 50 | 51 | NEW FEATURES 52 | 53 | o added a new argument `output` to usage() 54 | 55 | BUG FIXES 56 | 57 | o fixed yihui/knitr#918: when code is NULL, parse() will hang (with a question 58 | mark waiting for input) 59 | 60 | CHANGES IN formatR VERSION 1.0 61 | 62 | NEW FEATURES 63 | 64 | o added a function tidy_app() to replace tidy.gui() in previous versions: 65 | tidy_app() launches a Shiny app in the browser to reformat R code. The 66 | gWidgets interface (e.g. GTK+) is no longer supported. See 67 | https://yihui.shinyapps.io/formatR/ for a live demo. 68 | 69 | BUG FIXES 70 | 71 | o the shebang #! is no longer treated as an R comment (thanks, Mirko 72 | Ebert, #36) 73 | 74 | MAJOR CHANGES 75 | 76 | o three functions were renamed (from the `foo.bar` style to `foo_bar`): 77 | `tidy.source()` (`tidy_source()`), `tidy.dir()` (`tidy_dir()`), and 78 | `tidy.eval()` (`tidy_eval()`) 79 | 80 | o the arguments of tidy_source() were renamed: `keep.comment` was renamed to 81 | `comment`, `keep.blank.line` -> `blank`, `replace.assign` -> `arrow`, 82 | `left.brace.newline` -> `brace.newline`, and `reindent.spaces` -> `indent`; 83 | similarly, the corresponding global options were also renamed: now you should 84 | use `options(formatR.comment)` instead of `options(keep.comment)`, 85 | `keep.blank.line` -> `formatR.blank`, and so on; see `?tidy_source` and 86 | http://yihui.name/formatR for details 87 | 88 | MINOR CHANGES 89 | 90 | o the usage() function returns the source code of the usage of a function 91 | now; in previous versions, it only returns NULL 92 | 93 | o added a new argument 'tidy' in usage() to make it possible not to 94 | reformat the usage code 95 | 96 | o tidy_source() may not work for R 3.0.0 or 3.0.1 if the code only 97 | contains comments due to a bug in base R, which has been fixed; if you use 98 | R 3.0, please upgrade to at least R 3.0.2 (R 2.15.x is not affected) 99 | 100 | CHANGES IN formatR VERSION 0.10 101 | 102 | MINOR CHANGES 103 | 104 | o the argument 'replace.assign' in tidy.source() will be treated as FALSE 105 | if there is no = in the code, because there is no need to replace = in 106 | such cases (this slighly improves the performance) 107 | 108 | o the PDF vignette was removed, and only the Markdown vignette was kept in 109 | this package, which uses the vignette engine knitr::docco_linear; see 110 | vignette('formatR', package = 'formatR') 111 | 112 | CHANGES IN formatR VERSION 0.9 113 | 114 | MAJOR CHANGES 115 | 116 | o tidy.source() uses utils::getParseData() to identify comments in R code 117 | under R 3.0.x, which is much more accurate than the regular expressions in 118 | previous versions; users are strongly recommended to try R 3.0.x (#25, #26) 119 | 120 | o changed the meaning of the argument 'width' in usage(); see documentation 121 | 122 | CHANGES IN formatR VERSION 0.8 123 | 124 | MAJOR CHANGES 125 | 126 | o tidy.source(text = character(0)) returns character(0) instead of '' 127 | 128 | o removed the (dark voodoo) functions parse.tidy() and deparse.tidy() as 129 | well as the operator "%InLiNe_IdEnTiFiEr%"; they were designed for the 130 | pgfSweave package, which has been archived on CRAN for a long time 131 | 132 | o the function unmask.source() is no longer exported 133 | 134 | CHANGES IN formatR VERSION 0.7 135 | 136 | BUG FIXES 137 | 138 | o backslashes in whole lines of comments can be correctly retained 139 | now (e.g. #' \code{1+1}) (thanks, KAPLAN Bernard) 140 | 141 | o the font button in tidy.gui() works again (#23) (thanks, Dason 142 | Kurkiewicz) 143 | 144 | o the option left.brace.newline was buggy; it did not work for empty 145 | lines 146 | 147 | MAJOR CHANGES 148 | 149 | o the option keep.space in tidy.source() was removed; the spaces 150 | before comments will not be faithfully kept 151 | 152 | NEW FEATURES 153 | 154 | o the number of spaces for indentation can be specified in 155 | tidy.gui() 156 | 157 | CHANGES IN formatR VERSION 0.6 158 | 159 | NEW FEATURES 160 | 161 | o the replace.assign argument is much more reliable now; it is based 162 | on the codetools package (code analysis) instead of regular 163 | expressions (big thanks to Kohske Takahashi) 164 | 165 | o replace.assign also works when keep.comment=FALSE; in previous 166 | versions, replace.assign=TRUE only applies to keep.comment=TRUE 167 | 168 | o tidy.source() gained a new argument 'left.brace.newline'; when set 169 | to TRUE, the left curly brace { will be moved to a new line (#18) 170 | (thanks, Jared Lander) 171 | 172 | MAJOR CHANGES 173 | 174 | o the 'text.tidy' component in the results of tidy.source() is a 175 | character vector of code blocks instead of code lines now, e.g. in 176 | previous versions, the result may be c('if (TRUE) {', '1', '}') 177 | (vector of length 3), but now it becomes 'if (TRUE) {\n1\n}'; each 178 | element of 'text.tidy' contains a minimal complete code block 179 | 180 | o potential dependency on the parser package has been removed 181 | (replaced by the codetools package); this also makes it more robust 182 | to use Unicode characters in R code now, see issue #13 for example 183 | 184 | o roxygen comments (#') will not be reflowed; this gives us control 185 | over which comments to be reflowed (sometimes we do not want 186 | comments to be wrapped and we can write them in the special roxygen 187 | comments) 188 | 189 | MINOR CHANGES 190 | 191 | o the results of tidy.source() (a list) only contain text.tidy and 192 | text.mask now; begin.comment and end.comment were removed since they 193 | were not used anywhere 194 | 195 | CHANGES IN formatR VERSION 0.5 196 | 197 | MAJOR CHANGES 198 | 199 | o the dependency on the parser package was removed because it was 200 | orphaned on CRAN; this affects two features: replace = with <- (the 201 | 'replace.assign' option in tidy.source()) and the identification of 202 | inline comments; tidy.source() will still work in most cases, but 203 | please keep in mind that (1) 'replace.assign=TRUE' will not be 204 | entirely reliable without parser (so use with extreme caution if you 205 | do not have parser installed) (2) if you want to write # in a 206 | character string, you must use double quotes, e.g. "here is a #" 207 | will be fine whereas 'here is a #' is not; if you want to use quotes 208 | in comments, please always use single quotes, e.g. # 'single quotes' 209 | (inline comments that contain double quotes will be dropped); if the 210 | parser package is available in your system (e.g. you installed it 211 | from the archived source on CRAN), everything will be the same as 212 | before 213 | 214 | o the default value for 'envir' in tidy.eval() was changed from 215 | globalenv() to parent.frame() 216 | 217 | MINOR CHANGES 218 | 219 | o \\t will no longer be replaced with \t when keep.space=TRUE 220 | because it is dangerous to do so; see #17 for an example 221 | 222 | CHANGES IN formatR VERSION 0.4 223 | 224 | NEW FEATURES 225 | 226 | o a new argument 'reindent.spaces' for tidy.source() to reindent the 227 | code with a specified number of spaces (e.g. 2) 228 | 229 | o comments will be reflowed as a whole block when possible (instead 230 | of being wrapped line by line); thanks, Paul Johnson 231 | 232 | MAJOR CHANGES 233 | 234 | o when a comment block is reflowed, the second and following lines 235 | will not be indented 236 | 237 | o the default value of the 'width.cutoff' argument in tidy.source() 238 | is getOption('width') now; in the past it was 75% of that width 239 | which was less intuitive 240 | 241 | o part of the documentation of tidy.source() has been moved to 242 | https://github.com/yihui/formatR/wiki/ 243 | 244 | o internally the comments are preserved by putting them in an 245 | expression invisible("# comments"); in past versions comments were 246 | retained in assignments; this change should not affect end users 247 | 248 | BUG FIXES 249 | 250 | o fixed #16: \\ in comments are preserved correctly now 251 | 252 | 253 | CHANGES IN formatR VERSION 0.3-4 254 | 255 | MINOR CHANGES 256 | 257 | o slight tweaks to the vignette (stopped Sweave from adding 258 | \usepackage{Sweave} which introduces ae by default) 259 | 260 | o fixed the error message in tidy.source(), pointing users to the 261 | wiki page on GitHub (thanks, Gabor Grothendieck) 262 | 263 | 264 | CHANGES IN formatR VERSION 0.3-3 265 | 266 | MINOR CHANGES 267 | 268 | o functions unmask.source(), parse.tidy(), deparse.tidy() and the 269 | operator %InLiNe_IdEnTiFiEr% were marked as `internal' in 270 | documentation 271 | 272 | o the vignette is processed by the knitr package 273 | 274 | o fixed a buglet in usage() so it can process functions with dots 275 | correctly 276 | 277 | 278 | CHANGES IN formatR VERSION 0.3-2 279 | 280 | MINOR CHANGES 281 | 282 | o the parser package is imported (in previous versions formatR 283 | depends on parser); thanks, Romain Francois 284 | 285 | 286 | CHANGES IN formatR VERSION 0.3-1 287 | 288 | SIGNIFICANT CHANGES 289 | 290 | o the function formatR() was renamed to tidy.gui() which is a more 291 | meaningful name since it is used to create a GUI 292 | 293 | NEW FEATURES 294 | 295 | o usage() will tell if the function is S3 296 | 297 | o a wiki is set up as the manual for formatR: 298 | https://github.com/yihui/formatR/wiki 299 | 300 | o tidy.eval() can evaluate the code in a specified environment now; 301 | see the 'envir' argument 302 | 303 | MINOR CHANGES 304 | 305 | o keep.blank.line is TRUE by default now (was FALSE in previous 306 | versions), i.e. blank lines are preserved by default 307 | 308 | 309 | CHANGES IN formatR VERSION 0.2-4 310 | 311 | NEW FEATURES 312 | 313 | o a new function tidy.eval(): evaluate R code and insert the output 314 | masked in comments (following ##) 315 | 316 | o the empty lines before 'else' will be removed even if 317 | keep.blank.line = TRUE; it is ill-advised to use blank lines among 318 | incomplete code chunks, e.g. 319 | 320 | if (TRUE) 321 | 322 | {'this is a BAD style of R programming'} 323 | 324 | o tidy.source() reports the line number when errors occur, which can 325 | help users detect the problem in the R code more quickly (thanks, 326 | Hadley Wickham) 327 | 328 | 329 | CHANGES IN formatR VERSION 0.2-3 330 | 331 | NEW FEATURES 332 | 333 | o 'else ...' will be moved back to the last line so that we will no 334 | longer see an 'else' statement in a new line 335 | 336 | 337 | CHANGES IN formatR VERSION 0.2-2 338 | 339 | NEW FEATURES 340 | 341 | o formatR now uses the parser package to parse inline comments, 342 | which can guarantee that these comments will be correctly parsed (no 343 | longer uses the 'unsafe' regular expressions to parse comments, so 344 | forget about the previous rules of writing comments -- just write 345 | comments with an arbitrary number of spaces before # as you wish) 346 | 347 | o the use of parser also enabled a new feature: '=' can be 348 | replaced with '<-' wherever appropriate (for example, '=' in 349 | function arguments will not be replaced; only thoese equal signs 350 | which are used to assigning purposes can be replaced) 351 | 352 | o long roxygen comments will not be wrapped (i.e. comments begin 353 | with #' or ##') 354 | 355 | MINOR CHANGES 356 | 357 | o fixed a minor problem in the function usage() (out --> output) 358 | 359 | o comments after { will be moved to the next line (in previous 360 | versions, these comments will cause errors) 361 | 362 | 363 | CHANGES IN formatR VERSION 0.2-1 364 | 365 | MINOR CHANGES 366 | 367 | o the escape character '\' in comments of complete lines will be 368 | successfully preserved, which is especially useful for tidy.source() 369 | to format the roxygen comments since we usually write comments like 370 | "##' @author Someone \email{}" but "\e" is not a legal character in 371 | R (this will lead to errors in earlier versions of this package) 372 | 373 | 374 | CHANGES IN formatR VERSION 0.2-0 375 | 376 | NEW FEATURES 377 | 378 | o a new function usage() to print the formatted usage of a function 379 | 380 | 381 | CHANGES IN formatR VERSION 0.1-9 382 | 383 | NEW FEATURES 384 | 385 | o tidy.source() can wrap long comments into shorter ones now (this 386 | only applies to the whole lines of comments; the inline comments 387 | will not be wrapped since it is tricky to do so) 388 | 389 | MINOR CHANGES 390 | 391 | o '\t' will be parsed to ' ' when 'keep.space' is TRUE in 392 | tidy.source() (this might be undesirable, though) 393 | 394 | 395 | CHANGES IN formatR VERSION 0.1-8 396 | 397 | NEW FEATURES 398 | 399 | o new functions parse.tidy() and deparse.tidy() for the package 400 | pgfSweave to help tidy the source code in Sweave 401 | 402 | o a new function tidy.dir() to format all the R scripts under a 403 | directory 404 | 405 | o added a package vignette 406 | 407 | 408 | CHANGES IN formatR VERSION 0.1-7 409 | 410 | NEW FEATURES 411 | 412 | o full support to multi-byte characters in the formatR() GUI 413 | 414 | o a new function unmask.source() to obtain the real source code 415 | from the masked source 416 | 417 | o a new operator '%InLiNe_IdEnTiFiEr%' designed mainly for 418 | pgfSweave (mask the inline comments) 419 | 420 | 421 | CHANGES IN formatR VERSION 0.1-6 422 | 423 | NEW FEATURES 424 | 425 | o the inline comments will also be preserved in most cases (in 426 | earlier versions, only single lines of comments are preserved) 427 | 428 | o tidy.source() gained a new argument 'text' to accept a character 429 | vector as the source code 430 | 431 | o multi-byte characters are partially supported in the formatR() GUI 432 | now (full support will come in 0.1-7) 433 | 434 | --------------------------------------------------------------------------------