diff --git a/vector/v.info/local_proto.h b/vector/v.info/local_proto.h index 24873d53cf5..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 }; +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 2d695d03e96..a401c40dd48 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,11 +79,22 @@ 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. *format_ptr = PLAIN; } + 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 { *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 803c86bee94..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; } } @@ -201,6 +205,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,6 +249,17 @@ void print_columns(struct Map_info *Map, const char *input_opt, for (col = 0; col < ncols; col++) { switch (format) { case SHELL: + G_fatal_error(_("format=shell is not valid with -c flag.")); + break; + + case CSV: + fprintf(stdout, "%s,%s\n", + 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: // Backward Compatibility fprintf(stdout, "%s|%s\n", db_sqltype_name( db_get_column_sqltype(db_get_table_column(table, col))), @@ -354,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) */ @@ -368,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 { @@ -380,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; } } @@ -403,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) { @@ -434,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, @@ -452,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); @@ -467,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; } } @@ -479,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) { @@ -491,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) { @@ -522,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); @@ -549,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); @@ -892,6 +932,8 @@ void print_history(struct Map_info *Map, enum OutputFormat format) mapset_path[0] = '\0'; } break; + default: // case CSV, NONE + break; } } 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()