From 7297c13f51b871448678e0920beafa9d447ed820 Mon Sep 17 00:00:00 2001 From: Gulshan Kumar Date: Sun, 4 Jan 2026 11:53:36 +0530 Subject: [PATCH 1/5] added format=csv --- vector/v.info/local_proto.h | 2 +- vector/v.info/parse.c | 11 +++++++++-- vector/v.info/print.c | 9 ++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/vector/v.info/local_proto.h b/vector/v.info/local_proto.h index 24873d53cf5..ce4357a3f43 100644 --- a/vector/v.info/local_proto.h +++ b/vector/v.info/local_proto.h @@ -8,7 +8,7 @@ #define STR_LEN 1024 #define BUFSZ 256 -enum OutputFormat { PLAIN, SHELL, JSON }; +enum OutputFormat { PLAIN, SHELL, JSON, CSV }; /* level1.c */ int level_one_info(struct Map_info *); diff --git a/vector/v.info/parse.c b/vector/v.info/parse.c index 2d695d03e96..81e54684a5e 100644 --- a/vector/v.info/parse.c +++ b/vector/v.info/parse.c @@ -44,10 +44,11 @@ void parse_args(int argc, char **argv, char **input, char **field, int *history, topo_flag->guisection = _("Print"); format_opt = G_define_standard_option(G_OPT_F_FORMAT); - format_opt->options = "plain,shell,json"; + format_opt->options = "plain,shell,json,csv"; format_opt->descriptions = _("plain;Human readable text output;" "shell;shell script style text output;" - "json;JSON (JavaScript Object Notation);"); + "json;JSON (JavaScript Object Notation);" + "csv;CSV (Comma Separated Values);"); format_opt->answer = NULL; format_opt->required = NO; format_opt->guisection = _("Print"); @@ -78,6 +79,12 @@ void parse_args(int argc, char **argv, char **input, char **field, int *history, *print_content |= PRINT_CONTENT_TOPO; } } + else if (format_opt->answer && strcmp(format_opt->answer, "csv") == 0) { + if (!*columns) { + G_fatal_error(_("format=csv is only valid with -c flag.")); + } + *format_ptr = CSV; + } else if (!format_opt->answer && *print_content == PRINT_CONTENT_UNSET && !*columns && !*history) { // No flags and no format specified, default to plain. diff --git a/vector/v.info/print.c b/vector/v.info/print.c index 803c86bee94..1dfb95e71dd 100644 --- a/vector/v.info/print.c +++ b/vector/v.info/print.c @@ -201,6 +201,9 @@ void print_columns(struct Map_info *Map, const char *input_opt, "layer <%s>:\n"), field_opt); } + if (format == CSV) { + fprintf(stdout, "%s,%s\n", "name", "sql_type"); + } if ((fi = Vect_get_field2(Map, field_opt)) == NULL) { Vect_close(Map); @@ -242,7 +245,11 @@ void print_columns(struct Map_info *Map, const char *input_opt, for (col = 0; col < ncols; col++) { switch (format) { case SHELL: - fprintf(stdout, "%s|%s\n", + G_fatal_error(_("format=shell is not valid with -c flag.")); + break; + + case CSV: + fprintf(stdout, "%s,%s\n", db_sqltype_name( db_get_column_sqltype(db_get_table_column(table, col))), db_get_column_name(db_get_table_column(table, col))); From ece236294ff468031bc8d6bc877fc3b0be83244e Mon Sep 17 00:00:00 2001 From: Gulshan Kumar Date: Sun, 4 Jan 2026 11:59:17 +0530 Subject: [PATCH 2/5] backward compatibility --- vector/v.info/local_proto.h | 2 +- vector/v.info/parse.c | 5 +++++ vector/v.info/print.c | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/vector/v.info/local_proto.h b/vector/v.info/local_proto.h index ce4357a3f43..b89b00ef8aa 100644 --- a/vector/v.info/local_proto.h +++ b/vector/v.info/local_proto.h @@ -8,7 +8,7 @@ #define STR_LEN 1024 #define BUFSZ 256 -enum OutputFormat { PLAIN, SHELL, JSON, CSV }; +enum OutputFormat { PLAIN, SHELL, JSON, CSV, NONE }; /* level1.c */ int level_one_info(struct Map_info *); diff --git a/vector/v.info/parse.c b/vector/v.info/parse.c index 81e54684a5e..03d390442ac 100644 --- a/vector/v.info/parse.c +++ b/vector/v.info/parse.c @@ -90,6 +90,11 @@ void parse_args(int argc, char **argv, char **input, char **field, int *history, // No flags and no format specified, default to plain. *format_ptr = PLAIN; } + else if (!format_opt->answer && *columns) { + // Backward compatibilty: sep should be pipe, and header should be + // skipped sep and header is handled during printing + *format_ptr = NONE; + } else { *format_ptr = SHELL; // If flags are specified with format=shell, obey them just as for JSON. diff --git a/vector/v.info/print.c b/vector/v.info/print.c index 1dfb95e71dd..e782a8b6541 100644 --- a/vector/v.info/print.c +++ b/vector/v.info/print.c @@ -255,6 +255,13 @@ void print_columns(struct Map_info *Map, const char *input_opt, db_get_column_name(db_get_table_column(table, col))); break; + case NONE: + fprintf(stdout, "%s|%s\n", + db_sqltype_name( + db_get_column_sqltype(db_get_table_column(table, col))), + db_get_column_name(db_get_table_column(table, col))); + break; + case JSON: column_value = G_json_value_init_object(); column_object = G_json_object(column_value); From 2e89cb3b3220a74396e677de301b1b60815cb99a Mon Sep 17 00:00:00 2001 From: Gulshan Kumar Date: Sun, 4 Jan 2026 12:06:47 +0530 Subject: [PATCH 3/5] nitpicks --- vector/v.info/parse.c | 6 +++--- vector/v.info/print.c | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/v.info/parse.c b/vector/v.info/parse.c index 03d390442ac..a401c40dd48 100644 --- a/vector/v.info/parse.c +++ b/vector/v.info/parse.c @@ -90,9 +90,9 @@ void parse_args(int argc, char **argv, char **input, char **field, int *history, // No flags and no format specified, default to plain. *format_ptr = PLAIN; } - else if (!format_opt->answer && *columns) { - // Backward compatibilty: sep should be pipe, and header should be - // skipped sep and header is handled during printing + else if (!format_opt->answer && *columns) { // -c without `format=` + // Backward compatibilty: sep should be pipe + header skipped + // sep and header is handled during printing *format_ptr = NONE; } else { diff --git a/vector/v.info/print.c b/vector/v.info/print.c index e782a8b6541..ef9d11f3e76 100644 --- a/vector/v.info/print.c +++ b/vector/v.info/print.c @@ -250,12 +250,12 @@ void print_columns(struct Map_info *Map, const char *input_opt, case CSV: fprintf(stdout, "%s,%s\n", - db_sqltype_name( - db_get_column_sqltype(db_get_table_column(table, col))), - db_get_column_name(db_get_table_column(table, col))); + db_get_column_name(db_get_table_column(table, col)), + db_sqltype_name(db_get_column_sqltype( + db_get_table_column(table, col)))); break; - case NONE: + case NONE: // Backward Compatibility fprintf(stdout, "%s|%s\n", db_sqltype_name( db_get_column_sqltype(db_get_table_column(table, col))), From 96686b56dff4deb062700688503c8f73d9a7fffc Mon Sep 17 00:00:00 2001 From: Gulshan Kumar Date: Wed, 7 Jan 2026 12:01:10 +0530 Subject: [PATCH 4/5] ci failure fix --- vector/v.info/print.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vector/v.info/print.c b/vector/v.info/print.c index ef9d11f3e76..30d64b5336e 100644 --- a/vector/v.info/print.c +++ b/vector/v.info/print.c @@ -74,6 +74,8 @@ void print_region(struct Map_info *Map, enum OutputFormat format, G_json_object_set_number(root_object, "top", box.T); G_json_object_set_number(root_object, "bottom", box.B); break; + default: // case CSV, NONE + break; } } @@ -172,6 +174,8 @@ void print_topo(struct Map_info *Map, enum OutputFormat format, } G_json_object_set_number(root_object, "primitives", nprimitives); G_json_object_set_boolean(root_object, "map3d", Vect_is_3d(Map)); + default: // case NONE, CSV + break; } } @@ -368,6 +372,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, G_json_object_set_string(root_object, "source_date", Vect_get_map_date(Map)); break; + default: // case CSV, NONE + break; } /* This shows the TimeStamp (if present) */ @@ -382,6 +388,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, case JSON: G_json_object_set_string(root_object, "timestamp", timebuff); break; + default: // case CSV, NONE + break; } } else { @@ -394,6 +402,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, case JSON: G_json_object_set_null(root_object, "timestamp"); break; + default: // case CSV, NONE + break; } } @@ -417,6 +427,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, Vect_get_finfo_dsn_name(Map)); G_json_object_set_string(root_object, "feature_type", geom_type); break; + default: // case CSV, NONE + break; } } else if (map_type == GV_FORMAT_POSTGIS) { @@ -448,6 +460,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, finfo->pg.geom_column); G_json_object_set_string(root_object, "feature_type", geom_type); break; + default: // case CSV, NONE + break; } topo_format = Vect_get_finfo_topology_info(Map, &toposchema_name, @@ -466,6 +480,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, G_json_object_set_string(root_object, "pg_topo_column", topogeom_column); break; + default: // case CSV, NONE + break; } } G_free(topogeom_column); @@ -481,6 +497,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, case JSON: G_json_object_set_string(root_object, "format", maptype_str); break; + default: + break; } } @@ -493,6 +511,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, case JSON: G_json_object_set_number(root_object, "level", Vect_level(Map)); break; + default: // case CSV, NONE + break; } if (Vect_level(Map) > 0) { switch (format) { @@ -505,6 +525,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, G_json_object_set_number(root_object, "num_dblinks", Vect_get_num_dblinks(Map)); break; + default: // case CSV, NONE + break; } if (Vect_get_num_dblinks(Map) > 0) { @@ -536,6 +558,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, G_json_object_set_string(root_object, "attribute_primary_key", fi->key); break; + default: // case CSV, NONE + break; } } Vect_destroy_field_info(fi); @@ -563,6 +587,8 @@ void print_shell(struct Map_info *Map, const char *field_opt, Vect_get_thresh(Map)); G_json_object_set_string(root_object, "comment", Vect_get_comment(Map)); break; + default: // case CSV, NONE + break; } G_free(finfo_lname); G_free((void *)maptype_str); @@ -906,6 +932,8 @@ void print_history(struct Map_info *Map, enum OutputFormat format) mapset_path[0] = '\0'; } break; + default: // case CSV, NONE + break; } } From 93847e9a8586e3db8716c56bf24ba763786e883d Mon Sep 17 00:00:00 2001 From: Gulshan Kumar Date: Wed, 7 Jan 2026 13:01:28 +0530 Subject: [PATCH 5/5] added tests --- vector/v.info/testsuite/test_vinfo.py | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/vector/v.info/testsuite/test_vinfo.py b/vector/v.info/testsuite/test_vinfo.py index 7c20e1889f1..8237deeb551 100644 --- a/vector/v.info/testsuite/test_vinfo.py +++ b/vector/v.info/testsuite/test_vinfo.py @@ -4,6 +4,7 @@ from grass.gunittest.main import test from grass.gunittest.gmodules import SimpleModule +from grass.exceptions import CalledModuleError class TestVInfo(TestCase): @@ -472,6 +473,34 @@ def test_database_table(self): reference={"INTEGER": "cat", "DOUBLE PRECISION": "elevation"}, ) + def test_column_csv_format(self): + """Test v.info -c format=csv output""" + expected = "name,sql_type\ncat,INTEGER\nelevation,DOUBLE PRECISION\n" + + module = SimpleModule( + "v.info", map=self.test_vinfo_with_db, flags="c", format="csv" + ) + self.runModule(module) + self.assertEqual(module.outputs.stdout, expected) + + def test_column_default_format(self): + """Test backward compatibility -c""" + expected = "INTEGER|cat\nDOUBLE PRECISION|elevation\n" + + module = SimpleModule("v.info", map=self.test_vinfo_with_db, flags="c") + self.runModule(module) + self.assertEqual(module.outputs.stdout, expected) + + def test_error_invalid_flag(self): + """Test for error: `format=shell -c` and `format=csv` without -c""" + with self.assertRaises(CalledModuleError): + self.runModule( + "v.info", map=self.test_vinfo_with_db, flags="c", format="shell" + ) + + with self.assertRaises(CalledModuleError): + self.runModule("v.info", map=self.test_vinfo_with_db, format="csv") + if __name__ == "__main__": test()