Skip to content

Commit

Permalink
g.mapsets: Add JSON output (#2542)
Browse files Browse the repository at this point in the history
* g.mapsets: fixed indenting and added json flag  in preperation to add json output

* Added format options to list mapsets as plain, vertical, csv, or json

* g.mapsets: Added start testing listing mapsets with different formats

* g.mapsets: Reformated python with flake8 and black

* g.mapsets: Fixed indent conflict

* Fixed implicit declaration of function errors

* Fixed issues found in code review

* Added print flag to json output and added tests

* Simplified tests

* Apply clang format suggestions

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Updated code to use parson and fixed tests

* Updated docs

* Fixed mapsets array initalization issue

* Fixed bug setting JSON_ARRAY

* Removed unused parameter

* Update general/g.mapsets/main.c

Fixed typo.

Co-authored-by: Anna Petrasova <kratochanna@gmail.com>

* Updated default separator to space

* Update general/g.mapsets/main.c

Switch GUI section to Print

Co-authored-by: Anna Petrasova <kratochanna@gmail.com>

* Update general/g.mapsets/main.c

Co-authored-by: Anna Petrasova <kratochanna@gmail.com>

* Update general/g.mapsets/main.c

Co-authored-by: Anna Petrasova <kratochanna@gmail.com>

* Removed unneeded logic

* Update general/g.mapsets/list.c

Co-authored-by: Nicklas Larsson <n_larsson@yahoo.com>

* Update general/g.mapsets/list.c

Co-authored-by: Nicklas Larsson <n_larsson@yahoo.com>

* Implmented suggestion from code review

* Removed duplicate code

* Refactored duplicate code and removed some comments

---------

Co-authored-by: Edouard Choinière <27212526+echoix@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Corey White <ctwhite48@gmail.com>
Co-authored-by: Anna Petrasova <kratochanna@gmail.com>
Co-authored-by: Nicklas Larsson <n_larsson@yahoo.com>
  • Loading branch information
6 people committed Apr 11, 2024
1 parent 49e1353 commit 8bf604c
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 38 deletions.
2 changes: 1 addition & 1 deletion general/g.mapsets/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ MODULE_TOPDIR = ../..

PGM = g.mapsets

LIBES = $(GISLIB)
LIBES = $(PARSONLIB) $(GISLIB)
DEPENDENCIES = $(GISDEP)

include $(MODULE_TOPDIR)/include/Make/Module.make
Expand Down
16 changes: 16 additions & 0 deletions general/g.mapsets/g.mapsets.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ <h3>Print available mapsets</h3>
PERMANENT user1 user2
</pre></div>

Mapsets can be also printed out as json by setting the format option to "json" (<b>format="json"</b>).

<div class="code">
<pre>
g.mapsets format="json" -l

{
"mapsets": [
"PERMANENT",
"user1",
"user2"
]
}
</pre>
</div>

<h3>Add new mapset</h3>

Add mapset 'user2' to the current mapset search path
Expand Down
94 changes: 69 additions & 25 deletions general/g.mapsets/list.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,97 @@
#include <grass/gis.h>
#include <grass/glocale.h>
#include "local_proto.h"
#include <grass/parson.h>

// Function to initialize a JSON object with a mapsets array
static JSON_Object *initialize_json_object()
{
JSON_Value *root_value = json_value_init_object();
if (!root_value) {
G_fatal_error(_("Failed to initialize JSON object. Out of memory?"));
}

JSON_Object *root_object = json_value_get_object(root_value);
json_object_set_value(root_object, "mapsets", json_value_init_array());

JSON_Array *mapsets = json_object_get_array(root_object, "mapsets");
if (!mapsets) {
json_value_free(root_value);
G_fatal_error(_("Failed to initialize mapsets array. Out of memory?"));
}

return root_object;
}

// Function to serialize and print JSON object
static void serialize_and_print_json_object(JSON_Value *root_value)
{
char *serialized_string = json_serialize_to_string_pretty(root_value);
if (!serialized_string) {
json_value_free(root_value);
G_fatal_error(_("Failed to serialize JSON to pretty format."));
}

fprintf(stdout, "%s\n", serialized_string);
json_free_serialized_string(serialized_string);
json_value_free(root_value);
}

void list_available_mapsets(const char **mapset_name, int nmapsets,
const char *fs)
{
int n;

G_message(_("Available mapsets:"));

for (n = 0; n < nmapsets; n++) {
for (int n = 0; n < nmapsets; n++) {
fprintf(stdout, "%s", mapset_name[n]);
if (n < nmapsets - 1) {
if (strcmp(fs, "newline") == 0)
fprintf(stdout, "\n");
else if (strcmp(fs, "space") == 0)
fprintf(stdout, " ");
else if (strcmp(fs, "comma") == 0)
fprintf(stdout, ",");
else if (strcmp(fs, "tab") == 0)
fprintf(stdout, "\t");
else
fprintf(stdout, "%s", fs);
fprintf(stdout, "%s", fs);
}
}
fprintf(stdout, "\n");
}

void list_accessible_mapsets(const char *fs)
{
int n;
const char *name;

G_message(_("Accessible mapsets:"));
for (n = 0; (name = G_get_mapset_name(n)); n++) {

for (int n = 0; (name = G_get_mapset_name(n)); n++) {
/* match each mapset to its numeric equivalent */
fprintf(stdout, "%s", name);
if (G_get_mapset_name(n + 1)) {
if (strcmp(fs, "newline") == 0)
fprintf(stdout, "\n");
else if (strcmp(fs, "space") == 0)
fprintf(stdout, " ");
else if (strcmp(fs, "comma") == 0)
fprintf(stdout, ",");
else if (strcmp(fs, "tab") == 0)
fprintf(stdout, "\t");
else
fprintf(stdout, "%s", fs);
fprintf(stdout, "%s", fs);
}
}
fprintf(stdout, "\n");
}

// Lists all accessible mapsets in JSON format
void list_accessible_mapsets_json()
{
const char *name;
JSON_Object *root_object = initialize_json_object();
JSON_Array *mapsets = json_object_get_array(root_object, "mapsets");

for (int n = 0; (name = G_get_mapset_name(n)); n++) {
json_array_append_string(mapsets, name);
}

serialize_and_print_json_object(
json_object_get_wrapping_value(root_object));
}

// Lists available mapsets from a provided array in JSON format
void list_avaliable_mapsets_json(const char **mapset_names, int nmapsets)
{
JSON_Object *root_object = initialize_json_object();
JSON_Array *mapsets = json_object_get_array(root_object, "mapsets");

for (int n = 0; n < nmapsets; n++) {
json_array_append_string(mapsets, mapset_names[n]);
}

serialize_and_print_json_object(
json_object_get_wrapping_value(root_object));
}
2 changes: 2 additions & 0 deletions general/g.mapsets/local_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ const char *substitute_mapset(const char *);
/* list.c */
void list_available_mapsets(const char **, int, const char *);
void list_accessible_mapsets(const char *);
void list_avaliable_mapsets_json(const char **, int);
void list_accessible_mapsets_json();
89 changes: 77 additions & 12 deletions general/g.mapsets/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
* Markus Neteler <neteler itc.it>,
* Moritz Lennert <mlennert club.worldonline.be>,
* Martin Landa <landa.martin gmail.com>,
* Huidae Cho <grass4u gmail.com>
* Huidae Cho <grass4u gmail.com>,
* Corey White <smortopahri gmail.com>
* PURPOSE: set current mapset path
* COPYRIGHT: (C) 1994-2009, 2012 by the GRASS Development Team
* COPYRIGHT: (C) 1994-2009, 2012-2024 by the GRASS Development Team
*
* This program is free software under the GNU General
* Public License (>=v2). Read the file COPYING that
Expand All @@ -33,6 +34,28 @@
#define OP_ADD 2
#define OP_REM 3

enum OutputFormat { PLAIN, JSON };

void fatal_error_option_value_excludes_flag(struct Option *option,
struct Flag *excluded,
const char *because)
{
if (!excluded->answer)
return;
G_fatal_error(_("The flag -%c is not allowed with %s=%s. %s"),
excluded->key, option->key, option->answer, because);
}

void fatal_error_option_value_excludes_option(struct Option *option,
struct Option *excluded,
const char *because)
{
if (!excluded->answer)
return;
G_fatal_error(_("The option %s is not allowed with %s=%s. %s"),
excluded->key, option->key, option->answer, because);
}

static void append_mapset(char **, const char *);

int main(int argc, char *argv[])
Expand All @@ -45,15 +68,15 @@ int main(int argc, char *argv[])
int no_tokens;
FILE *fp;
char path_buf[GPATH_MAX];
char *path, *fs;
char *path, *fsep;
int operation, nchoices;

enum OutputFormat format;
char **mapset_name;
int nmapsets;

struct GModule *module;
struct _opt {
struct Option *mapset, *op, *fs;
struct Option *mapset, *op, *format, *fsep;
struct Flag *print, *list, *dialog;
} opt;

Expand Down Expand Up @@ -82,10 +105,20 @@ int main(int argc, char *argv[])
opt.op->description = _("Operation to be performed");
opt.op->answer = "add";

opt.fs = G_define_standard_option(G_OPT_F_SEP);
opt.fs->label = _("Field separator for printing (-l and -p flags)");
opt.fs->answer = "space";
opt.fs->guisection = _("Print");
opt.format = G_define_option();
opt.format->key = "format";
opt.format->type = TYPE_STRING;
opt.format->required = YES;
opt.format->label = _("Output format for printing (-l and -p flags)");
opt.format->options = "plain,json";
opt.format->descriptions = "plain;Configurable plain text output;"
"json;JSON (JavaScript Object Notation);";
opt.format->answer = "plain";
opt.format->guisection = _("Print");

opt.fsep = G_define_standard_option(G_OPT_F_SEP);
opt.fsep->answer = NULL;
opt.fsep->guisection = _("Print");

opt.list = G_define_flag();
opt.list->key = 'l';
Expand Down Expand Up @@ -130,7 +163,27 @@ int main(int argc, char *argv[])
}
}

fs = G_option_to_separator(opt.fs);
if (strcmp(opt.format->answer, "json") == 0)
format = JSON;
else
format = PLAIN;
if (format == JSON) {
fatal_error_option_value_excludes_option(
opt.format, opt.fsep, _("Separator is part of the format."));
}

/* the field separator */
if (opt.fsep->answer) {
fsep = G_option_to_separator(opt.fsep);
}
else {
/* A different separator is needed to for each format and output. */
if (format == PLAIN) {
fsep = G_store(" ");
}
else
fsep = NULL; /* Something like a separator is part of the format. */
}

/* list available mapsets */
if (opt.list->answer) {
Expand All @@ -141,7 +194,13 @@ int main(int argc, char *argv[])
if (opt.mapset->answer)
G_warning(_("Option <%s> ignored"), opt.mapset->key);
mapset_name = get_available_mapsets(&nmapsets);
list_available_mapsets((const char **)mapset_name, nmapsets, fs);
if (format == JSON) {
list_avaliable_mapsets_json((const char **)mapset_name, nmapsets);
}
else {
list_available_mapsets((const char **)mapset_name, nmapsets, fsep);
}

exit(EXIT_SUCCESS);
}

Expand All @@ -150,7 +209,13 @@ int main(int argc, char *argv[])
G_warning(_("Flag -%c ignored"), opt.dialog->key);
if (opt.mapset->answer)
G_warning(_("Option <%s> ignored"), opt.mapset->key);
list_accessible_mapsets(fs);
if (format == JSON) {
list_accessible_mapsets_json();
}
else {
list_accessible_mapsets(fsep);
}

exit(EXIT_SUCCESS);
}

Expand Down
35 changes: 35 additions & 0 deletions general/g.mapsets/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Fixtures for Jupyter tests
Fixture for grass.jupyter.TimeSeries test
Fixture for ReprojectionRenderer test with simple GRASS location, raster, vector.
"""


from types import SimpleNamespace

import grass.script as gs
import pytest

TEST_MAPSETS = ["PERMANENT", "test1", "test2", "test3"]
ACCESSIBLE_MAPSETS = ["test3", "PERMANENT"]


@pytest.fixture(scope="module")
def simple_dataset(tmp_path_factory):
"""Start a session and create a test mapsets
Returns object with attributes about the dataset.
"""
tmp_path = tmp_path_factory.mktemp("simple_dataset")
location = "test"
gs.core._create_location_xy(tmp_path, location) # pylint: disable=protected-access
with gs.setup.init(tmp_path / location):
gs.run_command("g.proj", flags="c", epsg=26917)
gs.run_command("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10)
# Create Mock Mapsets
for mapset in TEST_MAPSETS:
gs.run_command("g.mapset", location=location, mapset=mapset, flags="c")

yield SimpleNamespace(
mapsets=TEST_MAPSETS, accessible_mapsets=ACCESSIBLE_MAPSETS
)

0 comments on commit 8bf604c

Please sign in to comment.