Skip to content

Commit

Permalink
v.info: Add JSON output for history (#4989)
Browse files Browse the repository at this point in the history
Signed-off-by: Nishant Bansal <[email protected]>
  • Loading branch information
NishantBansal2003 authored Feb 9, 2025
1 parent 2b7a3f3 commit d861511
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 6 deletions.
5 changes: 5 additions & 0 deletions vector/v.info/local_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define SHELL_BASIC 0x02
#define SHELL_REGION 0x04
#define SHELL_TOPO 0x08
#define STR_LEN 1024

enum OutputFormat { PLAIN, SHELL, JSON };

Expand All @@ -24,3 +25,7 @@ void print_columns(struct Map_info *, const char *, const char *,
void print_info(struct Map_info *);
void print_shell(struct Map_info *, const char *, enum OutputFormat,
JSON_Object *);
void parse_history_line(const char *, char *, char *, char *, char *, char *,
char *, char *);
void add_record_to_json(char *, char *, char *, char *, JSON_Array *, int);
void print_history(struct Map_info *, enum OutputFormat);
7 changes: 1 addition & 6 deletions vector/v.info/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,7 @@ int main(int argc, char *argv[])

if (hist_flag || col_flag) {
if (hist_flag) {
char buf[1001];

Vect_hist_rewind(&Map);
while (Vect_hist_read(buf, 1000, &Map) != NULL) {
fprintf(stdout, "%s\n", buf);
}
print_history(&Map, format);
}
else if (col_flag) {
print_columns(&Map, input_opt, field_opt, format);
Expand Down
128 changes: 128 additions & 0 deletions vector/v.info/print.c
Original file line number Diff line number Diff line change
Expand Up @@ -771,3 +771,131 @@ void print_info(struct Map_info *Map)
divider('+');
fprintf(stdout, "\n");
}

/*!
\brief Extracts and assigns values from a history line to command, gisdbase,
location, mapset, user, date, and mapset_path based on specific prefixes.
*/
void parse_history_line(const char *buf, char *command, char *gisdbase,
char *location, char *mapset, char *user, char *date,
char *mapset_path)
{
if (strncmp(buf, "COMMAND:", 8) == 0) {
sscanf(buf, "COMMAND: %[^\n]", command);
}
else if (strncmp(buf, "GISDBASE:", 9) == 0) {
sscanf(buf, "GISDBASE: %[^\n]", gisdbase);
}
else if (strncmp(buf, "LOCATION:", 9) == 0) {
sscanf(buf, "LOCATION: %s MAPSET: %s USER: %s DATE: %[^\n]", location,
mapset, user, date);

snprintf(mapset_path, GPATH_MAX, "%s/%s/%s", gisdbase, location,
mapset);
}
}

/*!
\brief Creates a JSON object with fields for command, user, date, and
mapset_path, appends it to a JSON array.
*/
void add_record_to_json(char *command, char *user, char *date,
char *mapset_path, JSON_Array *record_array,
int history_number)
{

JSON_Value *info_value = json_value_init_object();
if (info_value == NULL) {
G_fatal_error(_("Failed to initialize JSON object. Out of memory?"));
}
JSON_Object *info_object = json_object(info_value);

json_object_set_number(info_object, "history_number", history_number);
json_object_set_string(info_object, "command", command);
json_object_set_string(info_object, "mapset_path", mapset_path);
json_object_set_string(info_object, "user", user);
json_object_set_string(info_object, "date", date);

json_array_append_value(record_array, info_value);
}

/*!
\brief Reads history entries from a map, formats them based on the specified
output format (PLAIN, SHELL, or JSON), and prints the results.
*/
void print_history(struct Map_info *Map, enum OutputFormat format)
{
int history_number = 0;

char buf[STR_LEN] = {0};
char command[STR_LEN] = {0}, gisdbase[STR_LEN] = {0};
char location[STR_LEN] = {0}, mapset[STR_LEN] = {0};
char user[STR_LEN] = {0}, date[STR_LEN] = {0};
char mapset_path[GPATH_MAX] = {0};

JSON_Value *root_value = NULL, *record_value = NULL;
JSON_Object *root_object = NULL;
JSON_Array *record_array = NULL;

if (format == JSON) {
root_value = json_value_init_object();
if (root_value == NULL) {
G_fatal_error(
_("Failed to initialize JSON object. Out of memory?"));
}
root_object = json_object(root_value);

record_value = json_value_init_array();
if (record_value == NULL) {
G_fatal_error(_("Failed to initialize JSON array. Out of memory?"));
}
record_array = json_array(record_value);
}

Vect_hist_rewind(Map);
while (Vect_hist_read(buf, sizeof(buf) - 1, Map) != NULL) {
switch (format) {
case PLAIN:
case SHELL:
fprintf(stdout, "%s\n", buf);
break;
case JSON:
// Parse each line based on its prefix
parse_history_line(buf, command, gisdbase, location, mapset, user,
date, mapset_path);
if (command[0] != '\0' && mapset_path[0] != '\0' &&
user[0] != '\0' && date[0] != '\0') {
// Increment history counter
history_number++;

add_record_to_json(command, user, date, mapset_path,
record_array, history_number);

// Clear the input strings before processing new
// entries in the history file
command[0] = '\0';
user[0] = '\0';
date[0] = '\0';
mapset_path[0] = '\0';
}
break;
}
}

if (format == JSON) {
json_object_set_value(root_object, "records", record_value);

char *serialized_string = json_serialize_to_string_pretty(root_value);
if (!serialized_string) {
json_value_free(root_value);
G_fatal_error(_("Failed to initialize pretty JSON string."));
}
puts(serialized_string);

json_free_serialized_string(serialized_string);
json_value_free(root_value);
}
}
45 changes: 45 additions & 0 deletions vector/v.info/testsuite/test_vinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class TestVInfo(TestCase):
test_vinfo_no_db = "test_vinfo_no_db"
test_vinfo_with_db = "test_vinfo_with_db"
test_vinfo_with_db_3d = "test_vinfo_with_db_3d"
test_vinfo_with_hist = "test_vinfo_with_hist"

# All maps should be tested against these references
reference = {
Expand Down Expand Up @@ -56,6 +57,17 @@ def setUpClass(cls):
flags="z",
)

cls.runModule(
"v.random", output=cls.test_vinfo_with_hist, npoints=5, zmin=0, zmax=100
)

# For testing vector history file with multiple commands
cls.runModule(
"v.support",
map=cls.test_vinfo_with_hist,
cmdhist='v.mkgrid map="test_vinfo_with_hist" grid=10,10 type="point"',
)

cls.runModule("v.timestamp", map=cls.test_vinfo_with_db_3d, date="15 jan 1994")

@classmethod
Expand All @@ -69,6 +81,7 @@ def tearDownClass(cls):
cls.test_vinfo_no_db,
cls.test_vinfo_with_db,
cls.test_vinfo_with_db_3d,
cls.test_vinfo_with_hist,
],
)

Expand Down Expand Up @@ -271,6 +284,38 @@ def test_json_column(self):

self.assertDictEqual(expected_json, result)

def test_json_histroy(self):
"""Test the JSON output format of v.info with the history flag, using a history file containing multiple commands."""
module = SimpleModule(
"v.info", map=self.test_vinfo_with_hist, format="json", flags="h"
)
self.runModule(module)
result = json.loads(module.outputs.stdout)

expected_json = {
"records": [
{
"history_number": 1,
"command": 'v.random output="test_vinfo_with_hist" npoints=5 layer="-1" zmin=0 zmax=100 column_type="double precision"',
},
{
"history_number": 2,
"command": 'v.mkgrid map="test_vinfo_with_hist" grid=10,10 type="point"',
},
]
}

# The following fields vary depending on the test data's path,
# date, and user. Therefore, only check for their presence in
# the JSON output and not for their exact values.
remove_fields = ["mapset_path", "date", "user"]
for record in result["records"]:
for field in remove_fields:
self.assertIn(field, record)
record.pop(field)

self.assertDictEqual(expected_json, result)

def test_database_table(self):
"""Test the database table column and type of the two vector maps with attribute data"""
self.assertModuleKeyValue(
Expand Down

0 comments on commit d861511

Please sign in to comment.