Skip to content

Commit

Permalink
Add eol argument to write functions
Browse files Browse the repository at this point in the history
This allows writing with Windows newlines if desired.

Fixes #857
  • Loading branch information
jimhester committed Mar 20, 2020
1 parent ccc4dea commit 938ca0c
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 46 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

## Additional features and fixes

* `write_*()` functions gain a `eol =` argument to control the end of line character used (#857). This allows writing of CSV files with Windows newlines (CRLF) if desired.

* `write_excel_csv()` no longer outputs a byte order mark when appending to a file (#1075).

* The `read_*` functions now close properly all connections, including on
Expand Down
4 changes: 2 additions & 2 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ write_file_raw_ <- function(x, connection) {
invisible(.Call(`_readr_write_file_raw_`, x, connection))
}

stream_delim_ <- function(df, connection, delim, na, col_names, bom, quote_escape) {
.Call(`_readr_stream_delim_`, df, connection, delim, na, col_names, bom, quote_escape)
stream_delim_ <- function(df, connection, delim, na, col_names, bom, quote_escape, eol) {
.Call(`_readr_stream_delim_`, df, connection, delim, na, col_names, bom, quote_escape, eol)
}

46 changes: 24 additions & 22 deletions R/write.R
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
#' "double", "backslash" or "none". You can also use `FALSE`, which is
#' equivalent to "none". The default is to double the quotes, which is the
#' format excel expects.
#' @param eol The end of line character to use. Most commonly either "\n" for
#' Unix style newlines, or "\r\n" for Windows style newlines.
#' @return `write_*()` returns the input `x` invisibly.
#' @references Florian Loitsch, Printing Floating-Point Numbers Quickly and
#' Accurately with Integers, PLDI '10,
Expand All @@ -60,41 +62,41 @@
#'
#' \dontshow{setwd(.old_wd)}
write_delim <- function(x, path, delim = " ", na = "NA", append = FALSE,
col_names = !append, quote_escape = "double") {
col_names = !append, quote_escape = "double", eol = "\n") {
stopifnot(is.data.frame(x))

x_out <- x
x[] <- lapply(names(x), function(i) output_column(x[[i]], i))
stream_delim(x, path, delim = delim, col_names = col_names, append = append,
na = na, quote_escape = quote_escape)
na = na, quote_escape = quote_escape, eol = eol)

invisible(x_out)
}

#' @rdname write_delim
#' @export
write_csv <- function(x, path, na = "NA", append = FALSE, col_names = !append,
quote_escape = "double") {
quote_escape = "double", eol = "\n") {
write_delim(x, path, delim = ",", na = na, append = append,
col_names = col_names, quote_escape = quote_escape)
col_names = col_names, quote_escape = quote_escape, eol = eol)
}

#' @rdname write_delim
#' @export
write_csv2 <- function(x, path, na = "NA", append = FALSE, col_names = !append,
quote_escape = "double") {
quote_escape = "double", eol = "\n") {
x_out <- x
x <- change_decimal_separator(x, decimal_mark = ",")
write_delim(x, path, delim = ";", na = na, append = append,
col_names = col_names, quote_escape = quote_escape)
col_names = col_names, quote_escape = quote_escape, eol = eol)

invisible(x_out)
}

#' @rdname write_delim
#' @export
write_excel_csv <- function(x, path, na = "NA", append = FALSE,
col_names = !append, delim = ",", quote_escape = "double") {
col_names = !append, delim = ",", quote_escape = "double", eol = "\n") {

stopifnot(is.data.frame(x))

Expand All @@ -104,31 +106,31 @@ write_excel_csv <- function(x, path, na = "NA", append = FALSE,

x[] <- lapply(x, output_column)
stream_delim(x, path, delim, col_names = col_names, append = append,
na = na, bom = !append, quote_escape = quote_escape)
na = na, bom = !append, quote_escape = quote_escape, eol = eol)

invisible(x_out)
}

#' @rdname write_delim
#' @export
write_excel_csv2 <- function(x, path, na = "NA", append = FALSE,
col_names = !append, delim = ";", quote_escape = "double") {
col_names = !append, delim = ";", quote_escape = "double", eol = "\n") {
x_out <- x
x <- change_decimal_separator(x, decimal_mark = ",")

datetime_cols <- vapply(x, inherits, logical(1), "POSIXt")
x[datetime_cols] <- lapply(x[datetime_cols], format, "%Y/%m/%d %H:%M:%S")

x[] <- lapply(x, output_column)
write_excel_csv(x, path, na, append, col_names, delim, quote_escape = quote_escape)
write_excel_csv(x, path, na, append, col_names, delim, quote_escape = quote_escape, eol = eol)

invisible(x_out)
}

#' @rdname write_delim
#' @export
write_tsv <- function(x, path, na = "NA", append = FALSE, col_names = !append, quote_escape = "double") {
write_delim(x, path, delim = '\t', na = na, append = append, col_names = col_names, quote_escape = quote_escape)
write_tsv <- function(x, path, na = "NA", append = FALSE, col_names = !append, quote_escape = "double", eol = "\n") {
write_delim(x, path, delim = '\t', na = na, append = append, col_names = col_names, quote_escape = quote_escape, eol = eol)
}

#' Convert a data frame to a delimited string
Expand All @@ -154,32 +156,32 @@ write_tsv <- function(x, path, na = "NA", append = FALSE, col_names = !append, q
#' cat(format_csv(df))
#' @export
format_delim <- function(x, delim, na = "NA", append = FALSE,
col_names = !append, quote_escape = "double") {
col_names = !append, quote_escape = "double", eol = "\n") {
stopifnot(is.data.frame(x))

x[] <- lapply(x, output_column)
res <- stream_delim(df = x, path = NULL, delim = delim, col_names = col_names, append = append, na = na, quote_escape = quote_escape)
res <- stream_delim(df = x, path = NULL, delim = delim, col_names = col_names, append = append, na = na, quote_escape = quote_escape, eol = eol)
Encoding(res) <- "UTF-8"
res
}

#' @export
#' @rdname format_delim
format_csv <- function(x, na = "NA", append = FALSE, col_names = !append, quote_escape = "double") {
format_delim(x, delim = ",", na = na, append = append, col_names = col_names, quote_escape = quote_escape)
format_csv <- function(x, na = "NA", append = FALSE, col_names = !append, quote_escape = "double", eol = "\n") {
format_delim(x, delim = ",", na = na, append = append, col_names = col_names, quote_escape = quote_escape, eol = eol)
}

#' @export
#' @rdname format_delim
format_csv2 <- function(x, na = "NA", append = FALSE, col_names = !append, quote_escape = "double") {
format_csv2 <- function(x, na = "NA", append = FALSE, col_names = !append, quote_escape = "double", eol = "\n") {
x <- change_decimal_separator(x, decimal_mark = ",")
format_delim(x, delim = ";", na = na, append = append, col_names = col_names, quote_escape = quote_escape)
format_delim(x, delim = ";", na = na, append = append, col_names = col_names, quote_escape = quote_escape, eol = eol)
}

#' @export
#' @rdname format_delim
format_tsv <- function(x, na = "NA", append = FALSE, col_names = !append, quote_escape = "double") {
format_delim(x, delim = "\t", na = na, append = append, col_names = col_names, quote_escape = quote_escape)
format_tsv <- function(x, na = "NA", append = FALSE, col_names = !append, quote_escape = "double", eol = "\n") {
format_delim(x, delim = "\t", na = na, append = append, col_names = col_names, quote_escape = quote_escape, eol = eol)
}

#' Preprocess column for output
Expand Down Expand Up @@ -220,7 +222,7 @@ output_column.list <- function(x, name) {
stop("Flat files can't store the list column `", name, "`", call. = FALSE)
}

stream_delim <- function(df, path, append = FALSE, bom = FALSE, ..., quote_escape) {
stream_delim <- function(df, path, append = FALSE, bom = FALSE, ..., quote_escape, eol) {
quote_escape <- standardise_escape(quote_escape)

path <- standardise_path(path, input = FALSE)
Expand All @@ -233,7 +235,7 @@ stream_delim <- function(df, path, append = FALSE, bom = FALSE, ..., quote_escap
open(path, "wb")
}
}
stream_delim_(df, path, ..., bom = bom, quote_escape = quote_escape)
stream_delim_(df, path, ..., bom = bom, quote_escape = quote_escape, eol = eol)
}

change_decimal_separator <- function(x, decimal_mark = ",") {
Expand Down
15 changes: 11 additions & 4 deletions man/format_delim.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 15 additions & 6 deletions man/write_delim.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions src/RcppExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ BEGIN_RCPP
END_RCPP
}
// stream_delim_
std::string stream_delim_(const List& df, RObject connection, char delim, const std::string& na, bool col_names, bool bom, int quote_escape);
RcppExport SEXP _readr_stream_delim_(SEXP dfSEXP, SEXP connectionSEXP, SEXP delimSEXP, SEXP naSEXP, SEXP col_namesSEXP, SEXP bomSEXP, SEXP quote_escapeSEXP) {
std::string stream_delim_(const List& df, RObject connection, char delim, const std::string& na, bool col_names, bool bom, int quote_escape, const char* eol);
RcppExport SEXP _readr_stream_delim_(SEXP dfSEXP, SEXP connectionSEXP, SEXP delimSEXP, SEXP naSEXP, SEXP col_namesSEXP, SEXP bomSEXP, SEXP quote_escapeSEXP, SEXP eolSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Expand All @@ -364,7 +364,8 @@ BEGIN_RCPP
Rcpp::traits::input_parameter< bool >::type col_names(col_namesSEXP);
Rcpp::traits::input_parameter< bool >::type bom(bomSEXP);
Rcpp::traits::input_parameter< int >::type quote_escape(quote_escapeSEXP);
rcpp_result_gen = Rcpp::wrap(stream_delim_(df, connection, delim, na, col_names, bom, quote_escape));
Rcpp::traits::input_parameter< const char* >::type eol(eolSEXP);
rcpp_result_gen = Rcpp::wrap(stream_delim_(df, connection, delim, na, col_names, bom, quote_escape, eol));
return rcpp_result_gen;
END_RCPP
}
Expand Down Expand Up @@ -395,7 +396,7 @@ static const R_CallMethodDef CallEntries[] = {
{"_readr_write_lines_raw_", (DL_FUNC) &_readr_write_lines_raw_, 3},
{"_readr_write_file_", (DL_FUNC) &_readr_write_file_, 2},
{"_readr_write_file_raw_", (DL_FUNC) &_readr_write_file_raw_, 2},
{"_readr_stream_delim_", (DL_FUNC) &_readr_stream_delim_, 7},
{"_readr_stream_delim_", (DL_FUNC) &_readr_stream_delim_, 8},
{NULL, NULL, 0}
};

Expand Down
21 changes: 13 additions & 8 deletions src/write_delim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ void stream_delim_row(
int i,
char delim,
const std::string& na,
quote_escape_t escape) {
quote_escape_t escape,
const char* eol) {
int p = Rf_length(x);

for (int j = 0; j < p - 1; ++j) {
Expand All @@ -33,7 +34,7 @@ void stream_delim_row(
}
stream_delim(output, x.at(p - 1), i, delim, na, escape);

output << '\n';
output << eol;
}

bool needs_quote(const char* string, char delim, const std::string& na) {
Expand Down Expand Up @@ -92,7 +93,8 @@ void stream_delim(
const std::string& na,
bool col_names,
bool bom,
quote_escape_t escape) {
quote_escape_t escape,
const char* eol) {
int p = Rf_length(df);
if (p == 0)
return;
Expand All @@ -108,14 +110,14 @@ void stream_delim(
if (j != p - 1)
output << delim;
}
output << '\n';
output << eol;
}

RObject first_col = df[0];
int n = Rf_length(first_col);

for (int i = 0; i < n; ++i) {
stream_delim_row(output, df, i, delim, na, escape);
stream_delim_row(output, df, i, delim, na, escape, eol);
}
}

Expand All @@ -127,7 +129,8 @@ std::string stream_delim_(
const std::string& na,
bool col_names,
bool bom,
int quote_escape) {
int quote_escape,
const char* eol) {
if (connection == R_NilValue) {
std::ostringstream output;
stream_delim(
Expand All @@ -137,7 +140,8 @@ std::string stream_delim_(
na,
col_names,
bom,
static_cast<quote_escape_t>(quote_escape));
static_cast<quote_escape_t>(quote_escape),
eol);
return output.str();
} else {
boost::iostreams::stream<connection_sink> output(connection);
Expand All @@ -148,7 +152,8 @@ std::string stream_delim_(
na,
col_names,
bom,
static_cast<quote_escape_t>(quote_escape));
static_cast<quote_escape_t>(quote_escape),
eol);
}

return "";
Expand Down
Loading

0 comments on commit 938ca0c

Please sign in to comment.