Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

g.mapsets: Add JSON output #2542

Merged
merged 31 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
032620a
g.mapsets: fixed indenting and added json flag in preperation to add…
cwhite911 Aug 27, 2022
dfbf8cf
Added format options to list mapsets as plain, vertical, csv, or json
cwhite911 Aug 27, 2022
3cd14cc
g.mapsets: Added start testing listing mapsets with different formats
cwhite911 Aug 27, 2022
53e3905
g.mapsets: Reformated python with flake8 and black
cwhite911 Aug 27, 2022
0f05302
g.mapsets: Fixed indent conflict
cwhite911 Aug 27, 2022
5415520
Merge remote-tracking branch 'upstream/main' into g.mapset-json
cwhite911 Aug 27, 2022
a904c05
Fixed implicit declaration of function errors
cwhite911 Aug 27, 2022
d3da3fd
Fixed issues found in code review
cwhite911 Aug 28, 2022
8173965
Added print flag to json output and added tests
cwhite911 Aug 28, 2022
34dd435
Simplified tests
cwhite911 Aug 31, 2022
5fd4e05
Merge branch 'main' into g.mapset-json
echoix Mar 21, 2024
e5e834c
Apply clang format suggestions
echoix Mar 21, 2024
5622b70
Merge branch 'main' into g.mapset-json
cwhite911 Apr 4, 2024
fb21b77
Updated code to use parson and fixed tests
Apr 4, 2024
76dbf30
Updated docs
Apr 4, 2024
4e6b53b
Fixed mapsets array initalization issue
Apr 4, 2024
8163581
Fixed bug setting JSON_ARRAY
Apr 4, 2024
bdceb3d
Removed unused parameter
Apr 4, 2024
db0252e
Update general/g.mapsets/main.c
cwhite911 Apr 5, 2024
6c18a50
Updated default separator to space
Apr 5, 2024
8e99fec
Update general/g.mapsets/main.c
cwhite911 Apr 7, 2024
8961692
Update general/g.mapsets/main.c
cwhite911 Apr 7, 2024
06d04a6
Update general/g.mapsets/main.c
cwhite911 Apr 7, 2024
cf061e6
Merge branch 'main' into g.mapset-json
cwhite911 Apr 7, 2024
ace2374
Removed unneeded logic
Apr 7, 2024
26f47c3
Update general/g.mapsets/list.c
cwhite911 Apr 8, 2024
e7f815a
Update general/g.mapsets/list.c
cwhite911 Apr 8, 2024
4b8f58b
Merge branch 'main' into g.mapset-json
cwhite911 Apr 8, 2024
4d0794c
Implmented suggestion from code review
Apr 8, 2024
6c8509a
Removed duplicate code
Apr 8, 2024
4463bf1
Refactored duplicate code and removed some comments
Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions general/g.mapsets/list.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ void list_accessible_mapsets(const char *fs)
const char *name;

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

for (n = 0; (name = G_get_mapset_name(n)); n++) {
/* match each mapset to its numeric equivalent */
fprintf(stdout, "%s", name);
Expand All @@ -53,3 +54,32 @@ void list_accessible_mapsets(const char *fs)
}
fprintf(stdout, "\n");
}

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

fprintf(stdout, "{\"mapsets\": [");
for (n = 0; (name = G_get_mapset_name(n)); n++) {
cwhite911 marked this conversation as resolved.
Show resolved Hide resolved
fprintf(stdout, "\"%s\"", name);
if (G_get_mapset_name(n + 1)) {
fprintf(stdout, ",");
}
}
fprintf(stdout, "]}\n");
}

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

fprintf(stdout, "{\"mapsets\": [");
for (n = 0; n < nmapsets; n++) {
fprintf(stdout, "\"%s\"", mapset_name[n]);
if (n < nmapsets - 1) {
fprintf(stdout, ",");
}
}
fprintf(stdout, "]}\n");
}
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(const char *);
95 changes: 83 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-2022 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,32 @@
#define OP_ADD 2
#define OP_REM 3

enum OutputFormat
{
PLAIN,
JSON
};
echoix marked this conversation as resolved.
Show resolved Hide resolved

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,16 +72,16 @@ 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 @@ -85,10 +112,21 @@ 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);";
echoix marked this conversation as resolved.
Show resolved Hide resolved
opt.format->answer = "plain";
opt.format->guisection = _("Format");

opt.fsep = G_define_standard_option(G_OPT_F_SEP);
opt.fsep->answer = NULL;
opt.fsep->guisection = _("Format");
cwhite911 marked this conversation as resolved.
Show resolved Hide resolved

opt.list = G_define_flag();
opt.list->key = 'l';
Expand Down Expand Up @@ -133,7 +171,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"));
echoix marked this conversation as resolved.
Show resolved Hide resolved
}

/* 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("|");
cwhite911 marked this conversation as resolved.
Show resolved Hide resolved
}
else
fsep = NULL; /* Something like a separator is part of the format. */
echoix marked this conversation as resolved.
Show resolved Hide resolved
}

/* list available mapsets */
if (opt.list->answer) {
Expand All @@ -144,7 +202,14 @@ 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);
echoix marked this conversation as resolved.
Show resolved Hide resolved
}

exit(EXIT_SUCCESS);
}

Expand All @@ -153,7 +218,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(fsep);
cwhite911 marked this conversation as resolved.
Show resolved Hide resolved
}
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
)
98 changes: 98 additions & 0 deletions general/g.mapsets/tests/test_g_mapsets_list_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
############################################################################
#
# MODULE: Test of g.mapsets
# AUTHOR(S): Corey White <smortopahri gmail com>
# PURPOSE: Test parsing and structure of CSV and JSON outputs
# COPYRIGHT: (C) 2022 by Corey White the GRASS Development Team
#
# This program is free software under the GNU General Public
# License (>=v2). Read the file COPYING that comes with GRASS
# for details.
#
#############################################################################

"""Test parsing and structure of CSV and JSON outputs from g.mapsets"""

import json
import pytest
import grass.script as gs


@pytest.mark.parametrize("flag", ["p", "l", "lp"])
echoix marked this conversation as resolved.
Show resolved Hide resolved
@pytest.mark.parametrize(
"separator", ["newline", "space", "comma", "tab", "pipe", ",", None]
)
def test_plain_output(simple_dataset, separator, flag):
"""Test that the separators are properly applied"""
mapsets = simple_dataset.mapsets
mapsets_len = len(mapsets)
if flag == "p":
mapsets = simple_dataset.accessible_mapsets
mapsets_len = len(mapsets)

text = gs.read_command("g.mapsets", format="plain", separator=separator, flags=flag)

def _check_parsed_list(text, sep):
"""Asserts to run on for each separator"""
parsed_list = text.splitlines()

# Make sure new line conditions are handled correctly
if sep != "\n":
parsed_list = text.split(sep)
if flag == "l":
assert parsed_list[-1] == "test3\n"
if flag == "p":
assert parsed_list[-1] == "PERMANENT\n"
else:
if flag == "l":
assert parsed_list[-1] == "test3"
if flag == "p":
assert parsed_list[-1] == "PERMANENT"
echoix marked this conversation as resolved.
Show resolved Hide resolved

# Check the beginning of list is correct
if flag == "p":
assert parsed_list[0] == "test3"

if flag == "l":
assert parsed_list[0] == "PERMANENT"

assert len(parsed_list) == mapsets_len
assert text == sep.join(mapsets) + "\n"

if separator == "newline":
_check_parsed_list(text, "\n")
elif separator == "space":
_check_parsed_list(text, " ")
elif separator == "comma":
_check_parsed_list(text, ",")
elif separator == "tab":
assert text == "\t".join(mapsets) + "\n"
elif separator == "pipe":
assert text == "|".join(mapsets) + "\n"
elif separator == ",":
assert text == ",".join(mapsets) + "\n"
else:
# Default vallue
assert text == "|".join(mapsets) + "\n"
echoix marked this conversation as resolved.
Show resolved Hide resolved


def test_json_ouput(simple_dataset):
"""JSON format"""
text = gs.read_command("g.mapsets", format="json", flags="l")
data = json.loads(text)
assert list(data.keys()) == ["mapsets"]
assert isinstance(data["mapsets"], list)
assert len(data["mapsets"]) == 4
for mapset in simple_dataset.mapsets:
assert mapset in data["mapsets"]


def test_accessible_mapsets_json_ouput(simple_dataset):
"""JSON format"""
text = gs.read_command("g.mapsets", format="json", flags="p")
data = json.loads(text)
assert list(data.keys()) == ["mapsets"]
assert isinstance(data["mapsets"], list)
assert len(data["mapsets"]) == 2
for mapset in simple_dataset.accessible_mapsets:
assert mapset in data["mapsets"]