From 640f6f8a16c99d7f0ab7b65ca52c9edf38760a1e Mon Sep 17 00:00:00 2001 From: schochastics Date: Wed, 9 Jul 2025 14:16:21 +0200 Subject: [PATCH 1/3] implemented read/write from/to string --- R/foreign.R | 31 +++++++++++++--- tests/testthat/_snaps/foreign.md | 61 ++++++++++++++++++++++++++++++++ tests/testthat/test-foreign.R | 9 +++++ 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/R/foreign.R b/R/foreign.R index 4d21b41a36d..3904c8bbdb1 100644 --- a/R/foreign.R +++ b/R/foreign.R @@ -319,7 +319,17 @@ read_graph <- function( ), ... ) { - if ( + from_string <- is.character(file) && length(file) == 1 && !file.exists(file) + + if (from_string || inherits(file, "connection")) { + con <- if (inherits(file, "connection")) file else textConnection(file) + tmp <- tempfile() + writeLines(readLines(con), tmp) + if (!inherits(file, "connection")) { + close(con) + } + file <- tmp + } else if ( !is.character(file) || length(grep("://", file, fixed = TRUE)) > 0 || length(grep("~", file, fixed = TRUE)) > 0 @@ -345,7 +355,6 @@ read_graph <- function( res } - #' Writing the graph to a file in some format #' #' `write_graph()` is a general function for exporting graphs to foreign @@ -481,10 +490,16 @@ write_graph <- function( ... ) { ensure_igraph(graph) + + to_stdout <- identical(file, "") || + identical(file, stdout()) || + identical(file, "-") + if ( !is.character(file) || length(grep("://", file, fixed = TRUE)) > 0 || - length(grep("~", file, fixed = TRUE)) > 0 + length(grep("~", file, fixed = TRUE)) > 0 || + to_stdout ) { tmpfile <- TRUE origfile <- file @@ -509,12 +524,18 @@ write_graph <- function( if (tmpfile) { buffer <- read.graph.toraw(file) - write.graph.fromraw(buffer, origfile) + + if (to_stdout) { + raw_connection <- rawConnection(buffer) + cat(readLines(raw_connection), sep = "\n") + close(raw_connection) + } else { + write.graph.fromraw(buffer, origfile) + } } invisible(res) } - ################################################################ # Plain edge list format, not sorted ################################################################ diff --git a/tests/testthat/_snaps/foreign.md b/tests/testthat/_snaps/foreign.md index 5ff13f89152..f123038cb29 100644 --- a/tests/testthat/_snaps/foreign.md +++ b/tests/testthat/_snaps/foreign.md @@ -73,3 +73,64 @@ ! not_existing is not a valid graph type. i Must be one of r001, r005, r01, r02, m2D, m2Dr2, m2Dr4, m2Dr6, m3D, m3Dr2, m3Dr4, m3Dr6, m4D, m4Dr2, m4Dr4, m4Dr6, b03, b03m, ..., b09, and b09m. +# read/write from/to string works + + Code + write_graph(g, file = "", format = "graphml") + Output + + + + + + + + Ring graph + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/testthat/test-foreign.R b/tests/testthat/test-foreign.R index 13333a03f32..ec1296bc478 100644 --- a/tests/testthat/test-foreign.R +++ b/tests/testthat/test-foreign.R @@ -76,3 +76,12 @@ test_that("graph_from_graphdb works", { error = TRUE ) }) + +test_that("read/write from/to string works", { + g <- make_ring(10) + + expect_snapshot(write_graph(g, file = "", format = "graphml")) + str <- r"---(0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 0 9)---" + g2 <- read_graph(str, format = "edgelist", directed = FALSE) + expect_isomorphic(g, g2) +}) From 3a9c4e18ca06fc868de9b720f365a5be4e9eeed5 Mon Sep 17 00:00:00 2001 From: schochastics Date: Wed, 9 Jul 2025 14:21:01 +0200 Subject: [PATCH 2/3] updated docs --- R/foreign.R | 4 ++-- man/read.graph.Rd | 2 +- man/read_graph.Rd | 2 +- man/write.graph.Rd | 2 +- man/write_graph.Rd | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/foreign.R b/R/foreign.R index 3904c8bbdb1..894289e6581 100644 --- a/R/foreign.R +++ b/R/foreign.R @@ -174,7 +174,7 @@ write.graph.fromraw <- function(buffer, file) { #' @aliases LGL Pajek GraphML GML DL UCINET #' @param file The connection to read from. This can be a local file, or a #' `http` or `ftp` connection. It can also be a character string with -#' the file name or URI. +#' the file name or URI or contain the graph in a single string. #' @param format Character constant giving the file format. Right now #' `edgelist`, `pajek`, `ncol`, `lgl`, `graphml`, #' `dimacs`, `graphdb`, `gml` and `dl` are supported, @@ -362,7 +362,7 @@ read_graph <- function( #' #' @param graph The graph to export. #' @param file A connection or a string giving the file name to write the graph -#' to. +#' to. If file is `""`, `stdout()` or `"-"`, then the output is written directly to the terminal. #' @param format Character string giving the file format. Right now #' `pajek`, `graphml`, `dot`, `gml`, `edgelist`, #' `lgl`, `ncol`, `leda` and `dimacs` are implemented. As of igraph 0.4 diff --git a/man/read.graph.Rd b/man/read.graph.Rd index 9c9bf90676e..70942559de1 100644 --- a/man/read.graph.Rd +++ b/man/read.graph.Rd @@ -14,7 +14,7 @@ read.graph( \arguments{ \item{file}{The connection to read from. This can be a local file, or a \code{http} or \code{ftp} connection. It can also be a character string with -the file name or URI.} +the file name or URI or contain the graph in a single string.} \item{format}{Character constant giving the file format. Right now \code{edgelist}, \code{pajek}, \code{ncol}, \code{lgl}, \code{graphml}, diff --git a/man/read_graph.Rd b/man/read_graph.Rd index 05d08030b22..918eefe9830 100644 --- a/man/read_graph.Rd +++ b/man/read_graph.Rd @@ -20,7 +20,7 @@ read_graph( \arguments{ \item{file}{The connection to read from. This can be a local file, or a \code{http} or \code{ftp} connection. It can also be a character string with -the file name or URI.} +the file name or URI or contain the graph in a single string.} \item{format}{Character constant giving the file format. Right now \code{edgelist}, \code{pajek}, \code{ncol}, \code{lgl}, \code{graphml}, diff --git a/man/write.graph.Rd b/man/write.graph.Rd index 42637c42a62..f6c2866f39a 100644 --- a/man/write.graph.Rd +++ b/man/write.graph.Rd @@ -16,7 +16,7 @@ write.graph( \item{graph}{The graph to export.} \item{file}{A connection or a string giving the file name to write the graph -to.} +to. If file is \code{""}, \code{stdout()} or \code{"-"}, then the output is written directly to the terminal.} \item{format}{Character string giving the file format. Right now \code{pajek}, \code{graphml}, \code{dot}, \code{gml}, \code{edgelist}, diff --git a/man/write_graph.Rd b/man/write_graph.Rd index f1f4a99ca1d..56f24e4e7a5 100644 --- a/man/write_graph.Rd +++ b/man/write_graph.Rd @@ -16,7 +16,7 @@ write_graph( \item{graph}{The graph to export.} \item{file}{A connection or a string giving the file name to write the graph -to.} +to. If file is \code{""}, \code{stdout()} or \code{"-"}, then the output is written directly to the terminal.} \item{format}{Character string giving the file format. Right now \code{pajek}, \code{graphml}, \code{dot}, \code{gml}, \code{edgelist}, From 8c0d8c8a1af248a3f061af6b64469a55d2878cb3 Mon Sep 17 00:00:00 2001 From: schochastics Date: Wed, 9 Jul 2025 15:28:38 +0200 Subject: [PATCH 3/3] close on exit --- R/foreign.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/foreign.R b/R/foreign.R index 894289e6581..8fbd3b9ef0e 100644 --- a/R/foreign.R +++ b/R/foreign.R @@ -527,8 +527,8 @@ write_graph <- function( if (to_stdout) { raw_connection <- rawConnection(buffer) + on.exit(close(raw_connection)) cat(readLines(raw_connection), sep = "\n") - close(raw_connection) } else { write.graph.fromraw(buffer, origfile) }