From 9f8802f49d01eeeb9c7e651af8b3f34258a27acd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:26:34 +0000 Subject: [PATCH 1/6] Initial plan From 33941e4eb38e4a352027c43abab866078a4fd469 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:29:56 +0000 Subject: [PATCH 2/6] Fix Date/POSIXt handlers not applied with column.major = FALSE In Ryaml_yoink(), use copyMostAttrib() to preserve class attributes when extracting single elements from data frame columns for row-major output. This ensures Date and POSIXt handlers are triggered correctly. Fixes #161 Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- _codeql_detected_source_root | 1 + src/r_emit.c | 3 +++ tests/testthat/test-as_yaml.R | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/src/r_emit.c b/src/r_emit.c index 4f9b8ea..498ffe4 100644 --- a/src/r_emit.c +++ b/src/r_emit.c @@ -300,6 +300,9 @@ static SEXP Ryaml_yoink(SEXP s_vec, int index) RAW(s_tmp)[0] = RAW(s_vec)[index]; break; } + if (!factor) { + copyMostAttrib(s_vec, s_tmp); + } UNPROTECT(1); return s_tmp; diff --git a/tests/testthat/test-as_yaml.R b/tests/testthat/test-as_yaml.R index f07dadf..90201a3 100644 --- a/tests/testthat/test-as_yaml.R +++ b/tests/testthat/test-as_yaml.R @@ -479,3 +479,27 @@ test_that("no dots at end", { result <- yaml::as.yaml(list(eol = "\n", a = 1), line.sep = "\n") expect_equal(result, "eol: |2+\n\na: 1.0\n") }) + +test_that("Date handler works with column.major = FALSE", { + x <- data.frame(date = as.Date(c("2012-10-10", "2014-03-28"))) + handler <- list(Date = function(x) as.character(x)) + + result_col <- as.yaml(x, handlers = handler, column.major = TRUE) + result_row <- as.yaml(x, handlers = handler, column.major = FALSE) + + expect_equal(result_col, "date:\n- '2012-10-10'\n- '2014-03-28'\n") + expect_equal(result_row, "- date: '2012-10-10'\n- date: '2014-03-28'\n") +}) + +test_that("POSIXct handler works with column.major = FALSE", { + x <- data.frame( + time = as.POSIXct(c("2012-10-10", "2014-03-28"), tz = "UTC") + ) + handler <- list(POSIXct = function(x) format(x, "%Y-%m-%d")) + + result_col <- as.yaml(x, handlers = handler, column.major = TRUE) + result_row <- as.yaml(x, handlers = handler, column.major = FALSE) + + expect_equal(result_col, "time:\n- '2012-10-10'\n- '2014-03-28'\n") + expect_equal(result_row, "- time: '2012-10-10'\n- time: '2014-03-28'\n") +}) From 6175d251a62a0b38dbc07ace32d813776b2d2e1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:01:17 +0000 Subject: [PATCH 3/6] Simplify Ryaml_yoink: remove factor special-casing, always copyMostAttrib Remove the factor-specific code path that converted factors to STRSXP in Ryaml_yoink(). Instead, always copy the integer value and call copyMostAttrib() unconditionally. The existing emit_factor() in emit_object() handles factor resolution correctly. This ensures factor handlers work with column.major = FALSE, just like Date and POSIXt handlers. Added tests for factor columns. Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com> --- src/r_emit.c | 25 +++++-------------------- tests/testthat/test-as_yaml.R | 13 +++++++++++++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/r_emit.c b/src/r_emit.c index 498ffe4..2f74cf9 100644 --- a/src/r_emit.c +++ b/src/r_emit.c @@ -261,31 +261,18 @@ static yaml_scalar_style_t Ryaml_string_style(SEXP s_obj) /* Take a vector and an index and return another vector of size 1 */ static SEXP Ryaml_yoink(SEXP s_vec, int index) { - SEXP s_tmp = NULL, s_levels = NULL; - int type = 0, factor = 0, level_idx = 0; + SEXP s_tmp = NULL; + int type = 0; type = TYPEOF(s_vec); - factor = type == INTSXP && Ryaml_has_class(s_vec, "factor"); - PROTECT(s_tmp = allocVector(factor ? STRSXP : type, 1)); + PROTECT(s_tmp = allocVector(type, 1)); switch(type) { case LGLSXP: LOGICAL(s_tmp)[0] = LOGICAL(s_vec)[index]; break; case INTSXP: - if (factor) { - s_levels = getAttrib(s_vec, R_LevelsSymbol); - level_idx = INTEGER(s_vec)[index]; - if (level_idx == NA_INTEGER || level_idx < 1 || level_idx > LENGTH(s_levels)) { - SET_STRING_ELT(s_tmp, 0, NA_STRING); - } - else { - SET_STRING_ELT(s_tmp, 0, STRING_ELT(s_levels, level_idx - 1)); - } - } - else { - INTEGER(s_tmp)[0] = INTEGER(s_vec)[index]; - } + INTEGER(s_tmp)[0] = INTEGER(s_vec)[index]; break; case REALSXP: REAL(s_tmp)[0] = REAL(s_vec)[index]; @@ -300,9 +287,7 @@ static SEXP Ryaml_yoink(SEXP s_vec, int index) RAW(s_tmp)[0] = RAW(s_vec)[index]; break; } - if (!factor) { - copyMostAttrib(s_vec, s_tmp); - } + copyMostAttrib(s_vec, s_tmp); UNPROTECT(1); return s_tmp; diff --git a/tests/testthat/test-as_yaml.R b/tests/testthat/test-as_yaml.R index 90201a3..2403cf7 100644 --- a/tests/testthat/test-as_yaml.R +++ b/tests/testthat/test-as_yaml.R @@ -503,3 +503,16 @@ test_that("POSIXct handler works with column.major = FALSE", { expect_equal(result_col, "time:\n- '2012-10-10'\n- '2014-03-28'\n") expect_equal(result_row, "- time: '2012-10-10'\n- time: '2014-03-28'\n") }) + +test_that("factor column works with column.major = FALSE", { + x <- data.frame(x = factor(c("a", "b", "c"))) + result <- as.yaml(x, column.major = FALSE) + expect_equal(result, "- x: a\n- x: b\n- x: c\n") +}) + +test_that("factor handler works with column.major = FALSE", { + x <- data.frame(x = factor(c("a", "b", "c"))) + handler <- list(factor = function(x) paste0("level_", x)) + result <- as.yaml(x, handlers = handler, column.major = FALSE) + expect_equal(result, "- x: level_a\n- x: level_b\n- x: level_c\n") +}) From 67f6edaed006e14da993a71491b4c01451ec64d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kirill=20M=C3=BCller?= Date: Sat, 28 Feb 2026 12:09:48 +0100 Subject: [PATCH 4/6] Deleted --- _codeql_detected_source_root | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b4..0000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From 547d7ac1560bd07ab042fb4bda26d3a7a2f05789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kirill=20M=C3=BCller?= Date: Sat, 28 Feb 2026 12:09:55 +0100 Subject: [PATCH 5/6] Ignored --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2b9e9cb..abb70d3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ yaml_*.tar.gz *.Rproj docs inst/doc +_codeql_detected_source_root From f4f1798add3ae185f21e75a463fdafec1c4959a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kirill=20M=C3=BCller?= Date: Sat, 28 Feb 2026 12:12:21 +0100 Subject: [PATCH 6/6] NEWS --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index bf24868..f624bba 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # yaml (development version) +* Apply handlers also with `column.major = FALSE` (#164, @krlmlr). + +# yaml 2.3.12 + * Fixes for C API compliance. * Switched from `CHANGELOG` to `NEWS.md`.