Skip to content
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`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.
`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 @aitap for the suggestion, and @AmanKashyap0807 for the fix.


### Notes

1. {data.table} now depends on R 3.5.0 (2018).
Expand Down
36 changes: 36 additions & 0 deletions R/fread.R
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why catch an exception to immediately rethrow it? Arguably this is an opportunity to give a better error message, but on the other hand, this obscures the original traceback() and makes it harder to debug problems.

)
} else if (identical(os_type, "unix")) {
# Valid on macOS, Linux, BSD, etc.
clip_cmd = if (nzchar(Sys.which("pbpaste"))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you set cmd here and make the if (!is.null(cmd)) branch below do the rest of the work?

"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"
Comment on lines +76 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This choice should probably depend not on what's available in the $PATH, but what's actually running. It's possible to have a Wayland compositor & tools installed but an X11 session running, and vice versa.

For X11 clients to work, the DISPLAY environment variable is required to be set.

For Wayland, WAYLAND_DISPLAY may be set, but Wayland clients can work without it, defaulting to wayland-0. Moreover, a Wayland session is likely to be running with $DISPLAY set, ready to launch Xwayland.

So if WAYLAND_DISPLAY is set, it's definitely a Wayland session, but if only DISPLAY is, then it's probably an X11 session, but it could also be a Wayland session in disguise.

Why not follow identical(Sys.info()[["sysname"]], "Darwin")) when choosing to run pbpaste?

} 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)))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on non windows and non-unix this will error because of trimws(clip) but clip was never assigned

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now added return(data.table()) inside the else block for unsupported platforms.

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)) {
Expand Down
45 changes: 45 additions & 0 deletions inst/tests/tests.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid there's no portable way to test this without violating CRAN policy:

Packages should not write in the user’s home filespace (including clipboards)

'https://cran.r-project.org/src/contrib/PACKAGES.in' |> url() |> read.dcf() |> _[,'X-CRAN-Comment'] |> grep(pattern = 'clipb', value = TRUE)
# [1] "Archived on 2019-10-16 for policy violation.\n\nSometimes attempts to write to the clipboard."
# [2] "Archived on 2019-01-06 for policy violation.\n\nwriting to clipboard hung the check run."

Emptying the clipboard afterwards is not enough, as it could previously contain something else, e.g., an image, which we have no way to read and restore later.

# 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))
}
})
Loading