From 68b954eccc055d2c61cf9fd0f082df321c1448e1 Mon Sep 17 00:00:00 2001 From: saket0187 Date: Thu, 11 Jun 2026 16:03:40 +0530 Subject: [PATCH 1/3] Add formats --- temporal/CMakeLists.txt | 2 +- temporal/t.connect/Makefile | 2 +- temporal/t.connect/main.c | 117 +++++++++++++++--- temporal/t.connect/t.connect.md | 7 +- .../testsuite/test_t_connect_formats.py | 64 ++++++++++ 5 files changed, 172 insertions(+), 20 deletions(-) create mode 100644 temporal/t.connect/testsuite/test_t_connect_formats.py diff --git a/temporal/CMakeLists.txt b/temporal/CMakeLists.txt index e368b7b5f2f..8013228cb86 100644 --- a/temporal/CMakeLists.txt +++ b/temporal/CMakeLists.txt @@ -63,7 +63,7 @@ if(WITH_DOCS) generate_docs(temporalintro TARGET ALL_TEMPORAL_MODULES) endif() -build_program_in_subdir(t.connect DEPENDS grass_gis grass_temporal) +build_program_in_subdir(t.connect DEPENDS grass_gis grass_temporal grass_parson) foreach(temporal_DIR ${temporal_script_modules_list}) build_script_in_subdir(${temporal_DIR}) diff --git a/temporal/t.connect/Makefile b/temporal/t.connect/Makefile index bdd51ef3b7c..f11b93d785e 100644 --- a/temporal/t.connect/Makefile +++ b/temporal/t.connect/Makefile @@ -1,6 +1,6 @@ MODULE_TOPDIR = ../.. -LIBES = $(TEMPORALLIB) $(GISLIB) +LIBES = $(TEMPORALLIB) $(GISLIB) $(PARSONLIB) DEPENDENCIES = $(TEMPORALDEP) $(GISDEP) PGM = t.connect diff --git a/temporal/t.connect/main.c b/temporal/t.connect/main.c index 6aca5cc8115..563f13101d9 100644 --- a/temporal/t.connect/main.c +++ b/temporal/t.connect/main.c @@ -18,14 +18,21 @@ #include #include #include +#include + +enum OutputFormat { PLAIN, SHELL, JSON }; int main(int argc, char *argv[]) { dbConnection conn; struct Flag *print, *check_set_default, *def, *sh; - struct Option *driver, *database; + struct Option *driver, *database, *format_opt; struct GModule *module; + enum OutputFormat format; + G_JSON_Value *root_value = NULL; + G_JSON_Object *root_object = NULL; + /* Initialize the GIS calls */ G_gisinit(argv[0]); @@ -42,6 +49,15 @@ int main(int argc, char *argv[]) print->description = _("Print current connection parameters and exit"); print->guisection = _("Print"); + format_opt = G_define_standard_option(G_OPT_F_FORMAT); + format_opt->options = "plain,shell,json"; + format_opt->required = NO; + format_opt->answer = "plain"; + format_opt->descriptions = _("plain;Plain text output;" + "shell;Shell script style output;" + "json;JSON (JavaScript Object Notation)"); + format_opt->guisection = _("Print"); + check_set_default = G_define_flag(); check_set_default->key = 'c'; check_set_default->description = @@ -72,31 +88,102 @@ int main(int argc, char *argv[]) if (G_parser(argc, argv)) exit(EXIT_FAILURE); + if (format_opt->answer != NULL) { + if (strcmp(format_opt->answer, "json") == 0) { + format = JSON; + root_value = G_json_value_init_object(); + if (root_value == NULL) { + G_fatal_error( + _("Failed to initialize JSON object. Out of memory?")); + } + root_object = G_json_object(root_value); + } + else if (strcmp(format_opt->answer, "shell") == 0) { + format = SHELL; + } + else { + format = PLAIN; + } + } + else { + format = PLAIN; + } + + if (sh->answer) { + G_verbose_message( + _("Flag 'g' is deprecated and will be removed in a future " + "release. Please use format=shell instead.")); + if (format == JSON) { + /* Free memory before failing to prevent memory leaks */ + if (root_value) + G_json_value_free(root_value); + G_fatal_error(_("The -g flag cannot be used with format=json. " + "Please select only one output format.")); + } + format = SHELL; + print->answer = 1; + } + + if (format != PLAIN && !print->answer) { + G_fatal_error( + _("The -p flag is required when using the format option.")); + } + if (print->answer) { - if (sh->answer) { - if (tgis_get_connection(&conn) == DB_OK) { + if (tgis_get_connection(&conn) == DB_OK) { + switch (format) { + case SHELL: fprintf(stdout, "driver=%s\n", conn.driverName ? conn.driverName : ""); fprintf(stdout, "database=%s\n", conn.databaseName ? conn.databaseName : ""); + break; + + case PLAIN: + fprintf(stdout, "driver: %s\n", + conn.driverName ? conn.driverName : ""); + fprintf(stdout, "database: %s\n", + conn.databaseName ? conn.databaseName : ""); + break; + + case JSON: + if (conn.driverName) + G_json_object_set_string(root_object, "driver", + conn.driverName); + else + G_json_object_set_null(root_object, "driver"); + + if (conn.databaseName) + G_json_object_set_string(root_object, "database", + conn.databaseName); + else + G_json_object_set_null(root_object, "database"); + break; } - else - G_fatal_error(_("Temporal GIS database connection not defined. " - "Run t.connect.")); } else { - /* get and print connection */ - if (tgis_get_connection(&conn) == DB_OK) { - fprintf(stdout, "driver:%s\n", - conn.driverName ? conn.driverName : ""); - fprintf(stdout, "database:%s\n", - conn.databaseName ? conn.databaseName : ""); + if (root_value) + G_json_value_free(root_value); + G_fatal_error(_("Temporal GIS database connection not defined. " + "Run t.connect.")); + } + + if (format == JSON) { + char *json_string = G_json_serialize_to_string_pretty(root_value); + if (!json_string) { + G_json_value_free(root_value); + G_fatal_error(_("Failed to serialize JSON to pretty format.")); } - else - G_fatal_error(_("Temporal GIS database connection not defined. " - "Run t.connect.")); + + fputs(json_string, stdout); + fputc('\n', stdout); + + G_json_free_serialized_string(json_string); + G_json_value_free(root_value); } + fflush(stdout); + exit(EXIT_SUCCESS); } diff --git a/temporal/t.connect/t.connect.md b/temporal/t.connect/t.connect.md index c8ff153e6b3..5bbf19cbc98 100644 --- a/temporal/t.connect/t.connect.md +++ b/temporal/t.connect/t.connect.md @@ -5,10 +5,11 @@ default setting is that the temporal database of type *sqlite3* is located in the current mapset directory. The **-p** flag will display the current temporal database connection -parameters. +parameters. Use the **format** option to specify the output format—`plain`, + `shell`, or `json` — with `plain` being the default. -The **-pg** flag will display the current temporal database connection -parameters using shell style. +The **-g** flag is deprecated and will be removed in a future release. Please +use **format=shell** option with the **-p** flag instead. The **-c** flag will silently check if the temporal database connection parameters have been set, and if not will set them to use GRASS's diff --git a/temporal/t.connect/testsuite/test_t_connect_formats.py b/temporal/t.connect/testsuite/test_t_connect_formats.py new file mode 100644 index 00000000000..551a2ed9705 --- /dev/null +++ b/temporal/t.connect/testsuite/test_t_connect_formats.py @@ -0,0 +1,64 @@ +""" +Test output formats for t.connect (plain, shell and json) +""" + +import json + +from grass.gunittest.case import TestCase +from grass.gunittest.gmodules import SimpleModule + + +class TestTConnectFormat(TestCase): + @classmethod + def setUpClass(cls): + """Initialize a default connection to ensure there is data to print.""" + cls.runModule( + "t.connect", + driver="sqlite", + database="$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db", + ) + + def test_plain_format_values(self): + """Test default plain text output contains the exact values.""" + t_conn = SimpleModule("t.connect", flags="p") + self.assertModule(t_conn) + out = t_conn.outputs.stdout + + self.assertIn("driver: sqlite", out) + self.assertIn("database: $GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db", out) + + def test_shell_format_values(self): + """Test shell format outputs exact keys and values with '=' delimiter.""" + t_conn = SimpleModule("t.connect", flags="p", format="shell") + self.assertModule(t_conn) + out = t_conn.outputs.stdout + + self.assertIn("driver=sqlite", out) + self.assertIn("database=$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db", out) + + def test_json_format_values(self): + """Test JSON output parses correctly and contains the exact values.""" + t_conn = SimpleModule("t.connect", flags="p", format="json") + self.assertModule(t_conn) + out = t_conn.outputs.stdout + + try: + data = json.loads(out) + except ValueError: + self.fail("Output is not valid JSON") + + self.assertEqual(data.get("driver"), "sqlite") + self.assertEqual( + data.get("database"), "$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db" + ) + + def test_incompatible_flags(self): + """Ensure using -g with format=json triggers a fatal error.""" + t_conn = SimpleModule("t.connect", flags="g", format="json") + self.assertModuleFail(t_conn) + + +if __name__ == "__main__": + from grass.gunittest.main import test + + test() From 8e830f96d44f66b3229b23b07993965e306c8ace Mon Sep 17 00:00:00 2001 From: saket0187 Date: Tue, 16 Jun 2026 18:12:09 +0530 Subject: [PATCH 2/3] Add formats tests --- temporal/t.connect/main.c | 11 ++-- temporal/t.connect/tests/test_t_connect.py | 33 +++++++++- .../testsuite/test_t_connect_formats.py | 64 ------------------- 3 files changed, 38 insertions(+), 70 deletions(-) delete mode 100644 temporal/t.connect/testsuite/test_t_connect_formats.py diff --git a/temporal/t.connect/main.c b/temporal/t.connect/main.c index 563f13101d9..28bffee5dcf 100644 --- a/temporal/t.connect/main.c +++ b/temporal/t.connect/main.c @@ -72,9 +72,12 @@ int main(int argc, char *argv[]) sh = G_define_flag(); sh->key = 'g'; + sh->label = _("Print current connection parameters using shell style " + "and exit [deprecated]"); sh->description = - _("Print current connection parameter in shell style and exit"); - sh->guisection = _("Set"); + _("This flag is deprecated and will be removed in a future " + "release. Use format=shell instead."); + sh->guisection = _("Print"); driver = G_define_standard_option(G_OPT_DB_DRIVER); driver->options = "sqlite,pg"; @@ -140,9 +143,9 @@ int main(int argc, char *argv[]) break; case PLAIN: - fprintf(stdout, "driver: %s\n", + fprintf(stdout, "driver:%s\n", conn.driverName ? conn.driverName : ""); - fprintf(stdout, "database: %s\n", + fprintf(stdout, "database:%s\n", conn.databaseName ? conn.databaseName : ""); break; diff --git a/temporal/t.connect/tests/test_t_connect.py b/temporal/t.connect/tests/test_t_connect.py index c968fea5c30..e200b8e70c2 100644 --- a/temporal/t.connect/tests/test_t_connect.py +++ b/temporal/t.connect/tests/test_t_connect.py @@ -1,6 +1,8 @@ """Test current baseline functionality of t.connect""" -from grass.tools import Tools +import pytest + +from grass.tools import ToolError, Tools def test_no_connection(empty_session): @@ -28,8 +30,35 @@ def test_print_shell(t_connect_session): """Check that the shell format prints correctly.""" tools = Tools(session=t_connect_session.session) - result = tools.t_connect(flags="pg") + result = tools.t_connect(flags="p", format="shell") lines = result.text_split("\n") assert "driver=sqlite" in lines assert "database=$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db" in lines + + +def test_print_json(t_connect_session): + """Check that the JSON format parses correctly and matches connection values.""" + tools = Tools(session=t_connect_session.session) + + result = tools.t_connect(flags="p", format="json") + + assert result["driver"] == "sqlite" + assert result["database"] == "$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db" + + +@pytest.mark.parametrize("output_format", ["shell", "json"]) +def test_missing_print_flag(t_connect_session, output_format): + """Ensure omitting the -p flag when requesting a format triggers a fatal error.""" + tools = Tools(session=t_connect_session.session) + + with pytest.raises(ToolError): + tools.t_connect(format=output_format) + + +def test_incompatible_flags(t_connect_session): + """Ensure combining the deprecated -g flag with format=json triggers a fatal error.""" + tools = Tools(session=t_connect_session.session) + + with pytest.raises(ToolError): + tools.t_connect(flags="g", format="json") diff --git a/temporal/t.connect/testsuite/test_t_connect_formats.py b/temporal/t.connect/testsuite/test_t_connect_formats.py deleted file mode 100644 index 551a2ed9705..00000000000 --- a/temporal/t.connect/testsuite/test_t_connect_formats.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Test output formats for t.connect (plain, shell and json) -""" - -import json - -from grass.gunittest.case import TestCase -from grass.gunittest.gmodules import SimpleModule - - -class TestTConnectFormat(TestCase): - @classmethod - def setUpClass(cls): - """Initialize a default connection to ensure there is data to print.""" - cls.runModule( - "t.connect", - driver="sqlite", - database="$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db", - ) - - def test_plain_format_values(self): - """Test default plain text output contains the exact values.""" - t_conn = SimpleModule("t.connect", flags="p") - self.assertModule(t_conn) - out = t_conn.outputs.stdout - - self.assertIn("driver: sqlite", out) - self.assertIn("database: $GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db", out) - - def test_shell_format_values(self): - """Test shell format outputs exact keys and values with '=' delimiter.""" - t_conn = SimpleModule("t.connect", flags="p", format="shell") - self.assertModule(t_conn) - out = t_conn.outputs.stdout - - self.assertIn("driver=sqlite", out) - self.assertIn("database=$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db", out) - - def test_json_format_values(self): - """Test JSON output parses correctly and contains the exact values.""" - t_conn = SimpleModule("t.connect", flags="p", format="json") - self.assertModule(t_conn) - out = t_conn.outputs.stdout - - try: - data = json.loads(out) - except ValueError: - self.fail("Output is not valid JSON") - - self.assertEqual(data.get("driver"), "sqlite") - self.assertEqual( - data.get("database"), "$GISDBASE/$LOCATION_NAME/$MAPSET/tgis/sqlite.db" - ) - - def test_incompatible_flags(self): - """Ensure using -g with format=json triggers a fatal error.""" - t_conn = SimpleModule("t.connect", flags="g", format="json") - self.assertModuleFail(t_conn) - - -if __name__ == "__main__": - from grass.gunittest.main import test - - test() From f6f144d88bfe17ee68ab8dcb4ad1ba1de978be40 Mon Sep 17 00:00:00 2001 From: SAKET KUMAR MALL <72020337+saket0187@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:45:25 +0530 Subject: [PATCH 3/3] Update temporal/t.connect/t.connect.md Co-authored-by: Stefan Blumentrath --- temporal/t.connect/t.connect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temporal/t.connect/t.connect.md b/temporal/t.connect/t.connect.md index 5bbf19cbc98..fa04ed27b21 100644 --- a/temporal/t.connect/t.connect.md +++ b/temporal/t.connect/t.connect.md @@ -5,7 +5,7 @@ default setting is that the temporal database of type *sqlite3* is located in the current mapset directory. The **-p** flag will display the current temporal database connection -parameters. Use the **format** option to specify the output format—`plain`, +parameters. Use the **format** option to specify the output format: `plain`, `shell`, or `json` — with `plain` being the default. The **-g** flag is deprecated and will be removed in a future release. Please