Skip to content

Commit f5eb0e3

Browse files
authored
Store current platform line ending in use_rstudio() (r-lib#1072)
* Set default line ending in `use_rstudio()`. Fixes r-lib#1002 * Respect current project line ending in `write_utf8()`. Fixes r-lib#767.
1 parent 29054f9 commit f5eb0e3

12 files changed

+130
-27
lines changed

Diff for: NEWS.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# usethis (development version)
22

3+
* `use_rstudio()` now sets the `LineEndingConversion` to your `Posix` so that
4+
packages created using usethis always use LF line endings, regardless of
5+
who contributes to them (#1002).
6+
7+
* When writing files, usethis now respects line endings. Default line endings
8+
are taken from the `.Rproj` file (if available), otherwise the `DESCRIPTION`,
9+
otherwise the first file found in `R/`, then all else failing to your
10+
platform default (#767).
11+
312
* `use_testthat()` and `use_test()` now works in projects, not just packages
413
(#1017).
514

Diff for: R/line-ending.R

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
proj_line_ending <- function() {
2+
# First look in .Rproj file
3+
proj_path <- proj_path(paste0(project_name(), ".Rproj"))
4+
if (file_exists(proj_path)) {
5+
config <- read_utf8(proj_path)
6+
7+
if (any(grepl("^LineEndingConversion: Posix", config))) {
8+
return("\n")
9+
} else if (any(grepl("^LineEndingConversion: Windows", config))) {
10+
return("\r\n")
11+
}
12+
}
13+
14+
# Then try DESCRIPTION
15+
desc_path <- proj_path("DESCRIPTION")
16+
if (file_exists(desc_path)) {
17+
return(detect_line_ending(desc_path))
18+
}
19+
20+
# Then try any .R file
21+
r_path <- proj_path("R")
22+
if (dir_exists(r_path)) {
23+
r_files <- dir_ls(r_path, pattern = "\\.[rR]$")
24+
if (length(r_files) > 0) {
25+
return(detect_line_ending(r_files[[1]]))
26+
}
27+
}
28+
29+
# Then give up - this is used (for example), when writing the
30+
# first file into the package
31+
platform_line_ending()
32+
}
33+
34+
platform_line_ending <- function() {
35+
if (.Platform$OS.type == "windows") "\r\n" else "\n"
36+
}
37+
38+
detect_line_ending <- function(path) {
39+
samp <- suppressWarnings(readChar(path, nchars = 500))
40+
if (isTRUE(grepl("\r\n", samp))) "\r\n" else "\n"
41+
}

Diff for: R/rstudio.R

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@
1010
#' * Adds RStudio files to `.gitignore`
1111
#' * Adds RStudio files to `.Rbuildignore`, if project is a package
1212
#'
13+
#' @param line_ending Line ending
1314
#' @export
14-
use_rstudio <- function() {
15+
use_rstudio <- function(line_ending = c("posix", "windows")) {
16+
line_ending <- arg_match(line_ending)
17+
line_ending <- c("posix" = "Posix", "windows" = "Windows")[[line_ending]]
18+
1519
rproj_file <- paste0(project_name(), ".Rproj")
16-
new <- use_template("template.Rproj", rproj_file)
20+
new <- use_template("template.Rproj", rproj_file,
21+
data = list(line_ending = line_ending)
22+
)
1723

1824
use_git_ignore(".Rproj.user")
1925
if (is_package()) {

Diff for: R/utils.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ is_installed <- function(pkg) {
5858
pluck_chr <- function(l, what) vapply(l, `[[`, character(1), what)
5959

6060
is_testing <- function() {
61-
identical(Sys.getenv("TESTTHAT"), "true")
61+
identical(Sys.getenv("TESTTHAT"), "true") || identical(Sys.getenv("R_COVR"), "true")
6262
}
6363

6464
interactive <- function() {

Diff for: R/write.R

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#' @keywords internal
1515
#'
1616
#' @examples
17-
#' \dontshow{.old_wd <- setwd(tempdir())}
17+
#' \dontshow{.old_wd <- setwd(tempdir()); proj_set(force = TRUE)}
1818
#' write_union("a_file", letters[1:3])
1919
#' readLines("a_file")
2020
#' write_union("a_file", letters[1:5])
@@ -92,17 +92,17 @@ read_utf8 <- function(path, n = -1L) {
9292
base::readLines(path, n = n, encoding = "UTF-8", warn = FALSE)
9393
}
9494

95-
write_utf8 <- function(path, lines, append = FALSE) {
95+
write_utf8 <- function(path, lines, append = FALSE, line_ending = proj_line_ending()) {
9696
stopifnot(is.character(path))
9797
stopifnot(is.character(lines))
9898

99-
file_mode <- if (append) "a" else ""
100-
99+
file_mode <- if (append) "ab" else "wb"
101100
con <- file(path, open = file_mode, encoding = "utf-8")
102101
on.exit(close(con), add = TRUE)
103102

104-
lines <- paste0(lines, "\n", collapse = "")
105-
cat(lines, file = con, sep = "")
103+
# convert embedded newlines
104+
lines <- gsub("\r?\n", line_ending, lines)
105+
base::writeLines(enc2utf8(lines), con, sep = line_ending, useBytes = TRUE)
106106

107107
invisible(TRUE)
108108
}

Diff for: inst/templates/template.Rproj

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Encoding: UTF-8
99

1010
AutoAppendNewline: Yes
1111
StripTrailingWhitespace: Yes
12+
LineEndingConversion: {{line_ending}}
1213

1314
BuildType: Package
1415
PackageUseDevtools: Yes

Diff for: man/use_rstudio.Rd

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: man/write-this.Rd

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: tests/testthat/test-line-ending.R

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
test_that("can detect path from RStudio project file", {
2+
scoped_temporary_package()
3+
use_rstudio("posix")
4+
expect_equal(proj_line_ending(), "\n")
5+
6+
file_delete(proj_path(paste(paste0(project_name(), ".Rproj"))))
7+
use_rstudio("windows")
8+
expect_equal(proj_line_ending(), "\r\n")
9+
})
10+
11+
test_that("can detect path from DESCRIPTION or .R file", {
12+
scoped_temporary_project()
13+
14+
write_utf8(proj_path("DESCRIPTION"), c("x", "y", "z"), line_ending = "\r\n")
15+
expect_equal(proj_line_ending(), "\r\n")
16+
file_delete(proj_path("DESCRIPTION"))
17+
18+
dir_create(proj_path("R"))
19+
write_utf8(proj_path("R/test.R"), c("x", "y", "z"), line_ending = "\r\n")
20+
expect_equal(proj_line_ending(), "\r\n")
21+
})
22+
23+
test_that("falls back to platform specific encoding", {
24+
scoped_temporary_project()
25+
expect_equal(proj_line_ending(), platform_line_ending())
26+
})
27+
28+
test_that("correctly detect line encoding", {
29+
path <- file_temp()
30+
31+
con <- file(path, open = "wb")
32+
writeLines(c("a", "b", "c"), con, sep = "\n")
33+
close(con)
34+
expect_equal(detect_line_ending(path), "\n")
35+
36+
con <- file(path, open = "wb")
37+
writeLines(c("a", "b", "c"), con, sep = "\r\n")
38+
close(con)
39+
expect_equal(detect_line_ending(path), "\r\n")
40+
})

Diff for: tests/testthat/test-use-rstudio.R renamed to tests/testthat/test-rstudio.R

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
context("use_rstudio")
2-
31
test_that("use_rstudio() creates .Rproj file, named after directory", {
42
dir <- scoped_temporary_package(rstudio = FALSE)
53
use_rstudio()
64
rproj <- path_file(dir_ls(proj_get(), regexp = "[.]Rproj$"))
75
expect_identical(path_ext_remove(rproj), path_file(dir))
6+
7+
# Always uses POSIX line endings
8+
expect_equal(proj_line_ending(), "\n")
89
})
910

1011
test_that("a non-RStudio project is not recognized", {

Diff for: tests/testthat/test-write.R

+15-14
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,24 @@ test_that("write_over() leaves file 'as is'", {
7272
# https://github.com/r-lib/usethis/issues/514
7373
test_that("write_ut8 always produces a trailing newline", {
7474
path <- file_temp()
75-
write_utf8(path, "x")
76-
77-
if (identical(Sys.info()[["sysname"]], "Windows")) {
78-
expect_equal(readChar(path, 3), "x\r\n")
79-
} else {
80-
expect_equal(readChar(path, 2), "x\n")
81-
}
75+
write_utf8(path, "x", line_ending = "\n")
76+
expect_equal(readChar(path, 2), "x\n")
8277
})
8378

8479
test_that("write_ut8 can append text when requested", {
8580
path <- file_temp()
86-
write_utf8(path, "x")
87-
write_utf8(path, "x", append = TRUE)
81+
write_utf8(path, "x", line_ending = "\n")
82+
write_utf8(path, "x", line_ending = "\n", append = TRUE)
83+
84+
expect_equal(readChar(path, 4), "x\nx\n")
85+
})
86+
87+
test_that("write_utf8 respects line ending", {
88+
path <- file_temp()
89+
90+
write_utf8(path, "x", line_ending = "\n")
91+
expect_equal(detect_line_ending(path), "\n")
8892

89-
if (identical(Sys.info()[["sysname"]], "Windows")) {
90-
expect_equal(readChar(path, 6), "x\r\nx\r\n")
91-
} else {
92-
expect_equal(readChar(path, 4), "x\nx\n")
93-
}
93+
write_utf8(path, "x", line_ending = "\r\n")
94+
expect_equal(detect_line_ending(path), "\r\n")
9495
})

Diff for: usethis.Rproj

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ LaTeX: XeLaTeX
1414

1515
AutoAppendNewline: Yes
1616
StripTrailingWhitespace: Yes
17+
LineEndingConversion: Posix
1718

1819
BuildType: Package
1920
PackageUseDevtools: Yes

0 commit comments

Comments
 (0)