diff --git a/NEWS.md b/NEWS.md index 7cf63f0b7..9f8f172de 100644 --- a/NEWS.md +++ b/NEWS.md @@ -40,6 +40,8 @@ 5. Non-equi joins combining an equality condition with two inequality conditions on the same column (e.g., `on = .(id == id, val >= lo, val <= hi)`) no longer error, [#7641](https://github.com/Rdatatable/data.table/issues/7641). The internal `chmatchdup` remapping of duplicate `rightcols` was overwriting the original column indices, causing downstream code to reference non-existent columns. Thanks @tarun-t for the report and fix, and @aitap for the diagnosis. +`fread()` now supports reading from the system clipboard by passing `input="clipboard"`, implemented for Windows, macOS (via `pbpaste`), and Linux (via `wl-paste`, `xclip`, or `xsel`), [#1292](https://github.com/Rdatatable/data.table/issues/1292). Thanks @mbacou for the report, @ben-schwen and @aitab for the suggestion, and @AmanKashyap0807 for the fix. + ### Notes 1. {data.table} now depends on R 3.5.0 (2018). diff --git a/R/fread.R b/R/fread.R index bc8509c71..52b9299c5 100644 --- a/R/fread.R +++ b/R/fread.R @@ -63,6 +63,42 @@ yaml=FALSE, tmpdir=tempdir(), tz="UTC") # input is data itself containing at least one \n or \r } else if (startsWith(input, " ")) { stopf("input= contains no \\n or \\r, but starts with a space. Please remove the leading space, or use text=, file= or cmd=") + } else if (identical(tolower(input), "clipboard")) { + os_type = .Platform$OS.type + if (identical(os_type, "windows")) { + clip = tryCatch(utils::readClipboard(), + error = function(e) stopf("Reading clipboard failed on Windows: %s", conditionMessage(e)) + ) + } else if (identical(os_type, "unix")) { + # Valid on macOS, Linux, BSD, etc. + clip_cmd = if (nzchar(Sys.which("pbpaste"))) { + "pbpaste" + } else if (nzchar(Sys.which("wl-paste"))) { + "wl-paste --no-newline" + } else if (nzchar(Sys.which("xclip"))) { + "xclip -o -selection clipboard" + } else if (nzchar(Sys.which("xsel"))) { + "xsel --clipboard --output" + } else { + stopf("Clipboard reading on Unix-like systems requires 'pbpaste', 'wl-paste', 'xclip', or 'xsel' to be installed.") + } + clip = tryCatch(system(clip_cmd, intern = TRUE), + error = function(e) stopf("Reading clipboard failed: %s", conditionMessage(e)) + ) + status = attr(clip, "status") + if (!is.null(status) && status != 0L) { + stopf("Reading clipboard failed (exit %d). Ensure '%s' is working.", status, clip_cmd) + } + } else { + warning("Clipboard reading is not supported on this platform.", call. = FALSE) + return(data.table()) + } + if (!length(clip) || !any(nzchar(trimws(clip)))) { + stopf("Clipboard is empty.") + } + writeLines(clip, tmpFile <- tempfile(tmpdir=tmpdir)) + file = tmpFile + on.exit(unlink(tmpFile), add=TRUE) } else if (length(grep(' ', input, fixed=TRUE)) && !file.exists(gsub("^file://", "", input))) { # file name or path containing spaces is not a command. file.exists() doesn't understand file:// (#7550) cmd = input if (input_has_vars && getOption("datatable.fread.input.cmd.message", TRUE)) { diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 508bf6aa0..1da694772 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21520,3 +21520,48 @@ test(2365.1, melt(df_melt, id.vars=1:2), melt(dt_melt, id.vars=1:2)) df_dcast = data.frame(a = c("x", "y"), b = 1:2, v = 3:4) dt_dcast = data.table(a = c("x", "y"), b = 1:2, v = 3:4) test(2365.2, dcast(df_dcast, a ~ b, value.var = "v"), dcast(dt_dcast, a ~ b, value.var = "v")) + +# Test fread clipboard input on Windows (issue #1292) +if (.Platform$OS.type == "windows") local({ + temp = c("a\tb", "1\t2") + utils::writeClipboard(temp) + on.exit(utils::writeClipboard(""), add = TRUE) + test(2366, fread("clipboard"), data.table(a = 1L, b = 2L)) +}) + +# Test fread clipboard input on macOS (issue #1292) +if (.Platform$OS.type == "unix" && identical(Sys.info()[["sysname"]], "Darwin")) local({ + if (nzchar(Sys.which("pbcopy"))) { + con = pipe("pbcopy", "w") + writeLines(c("a\tb", "1\t2"), con) + close(con) + on.exit({ + cl = pipe("pbcopy", "w") + writeLines("", cl) + close(cl) + }, add=TRUE) + test(2366.1, fread("clipboard"), data.table(a=1L, b=2L)) + } +}) + +# Test fread clipboard input on Linux via xclip/xsel/wl-paste (issue #1292) +if (.Platform$OS.type == "unix" && identical(Sys.info()[["sysname"]], "Linux")) local({ + has_wl = nzchar(Sys.which("wl-copy")) && nzchar(Sys.which("wl-paste")) + has_xclip = nzchar(Sys.which("xclip")) + has_xsel = nzchar(Sys.which("xsel")) + writer = if (has_wl) "wl-copy" + else if (has_xclip) "xclip -i -selection clipboard" + else if (has_xsel) "xsel --clipboard --input" + else "" + if (nzchar(writer)) { + con = pipe(writer, "w") + writeLines(c("a\tb", "1\t2"), con) + close(con) + on.exit({ + cl = pipe(writer, "w") + writeLines("", cl) + close(cl) + }, add=TRUE) + test(2366.2, fread("clipboard"), data.table(a=1L, b=2L)) + } +})