Skip to content

Commit

Permalink
g.citation: fix module output parameter (#819)
Browse files Browse the repository at this point in the history
* Extend the tests to test the output parameter of the module
  • Loading branch information
tmszi committed Oct 22, 2022
1 parent ee8fbcb commit b0ee62f
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 53 deletions.
231 changes: 178 additions & 53 deletions src/general/g.citation/g.citation.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,12 @@ def is_middle_initial(text):
return {"given": given, "particle": particle, "family": family, "suffix": suffix}


def print_cff(citation):
def print_cff(citation, output):
"""Create Citation File Format file from citation dictionary
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
>>> authors = [{'name': 'Joe Doe', 'orcid': '0000-0000-0000-0000'}]
>>> cit = {'module': 'g.tst', 'authors': authors, 'year': 2011}
>>> cit.update({'grass-version': '7.4.1'})
Expand All @@ -562,73 +565,98 @@ def print_cff(citation):
date-released: 2018-06-07
license: GPL-2.0-or-later
"""
print("cff-version: 1.0.3")
print('message: "If you use this software, please cite it as below."')
print("authors:")
print("cff-version: 1.0.3", file=output)
print(
'message: "If you use this software, please cite it as below."',
file=output,
)
print("authors:", file=output)
for author in citation["authors"]:
# note: CFF 1.0.3 specifies mandatory family, mandatory given,
# optional particle (e.g. van), and optional suffix (e.g. III),
# best shot should be taken for names which don't include family
# or given or which have different order
# here we just split based on first space into given and family
name = author_name_to_cff(author["name"])
print(" - family-names:", name["family"])
print(" given-names:", name["given"])
print(" - family-names:", name["family"], file=output)
print(" given-names:", name["given"], file=output)
if author["orcid"]:
print(" orcid:", author["orcid"])
print('title: "GRASS GIS: ', citation["module"], ' module"', sep="")
print("version:", citation["grass-version"])
print(" orcid:", author["orcid"], file=output)
print(
'title: "GRASS GIS: ',
citation["module"],
' module"',
sep="",
file=output,
)
print("version:", citation["grass-version"], file=output)
# CFF 1.0.3 does not say expplicitely except for Date (so not any
# string), so assuming YAML timestamp
# (http://yaml.org/type/timestamp.html)
# now we have only the year, so using Jan 1
print("date-released:", citation["grass-build-date"])
print("date-released:", citation["grass-build-date"], file=output)
# license string according to https://spdx.org/licenses/
# we know license of GRASS modules should be GPL>=2
print("license: GPL-2.0-or-later")
print("license: GPL-2.0-or-later", file=output)
if citation.get("keywords", None):
print("keywords:")
print("keywords:", file=output)
for keyword in citation["keywords"]:
print(" -", keyword)
print(" -", keyword, file=output)
if citation.get("references", None):
print("references:")
print("references:", file=output)
for reference in citation["references"]:
# making sure scope, type, and title are first
if reference.get("scope", None):
print(" - scope:", reference["scope"])
print(" type:", reference["type"])
print(" - scope:", reference["scope"], file=output)
print(" type:", reference["type"], file=output)
else:
print(" - type:", reference["type"])
print(" title:", reference["title"])
print(" - type:", reference["type"], file=output)
print(" title:", reference["title"], file=output)
for key, value in reference.items():
if key in ["scope", "type", "title"]:
continue # already handled
# TODO: add general serialization to YAML
elif key == "authors":
print(" authors:")
print(" authors:", file=output)
for author in value:
# special order for the name of entity
if "name" in author:
print(" - name: {name}".format(**author))
print(
" - name: {name}".format(**author),
file=output,
)
elif "family-names" in author:
print(
" - family-names: {family-names}".format(**author)
" - family-names: {family-names}".format(**author),
file=output,
)
for akey, avalue in author.items():
if akey == "name":
continue
print(" {akey}: {avalue}".format(**locals()))
print(
" {akey}: {avalue}".format(**locals()),
file=output,
)
elif key == "keywords":
print(" keywords:")
print(" keywords:", file=output)
for keyword in value:
print(" - {keyword}".format(**locals()))
print(
" - {keyword}".format(**locals()),
file=output,
)
else:
print(" {key}: {value}".format(**locals()))
print(
" {key}: {value}".format(**locals()),
file=output,
)


def print_bibtex(citation):
def print_bibtex(citation, output):
"""Create BibTeX entry from citation dictionary
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
>>> print_bibtex({'module': 'g.tst', 'authors': [{'name': 'Joe Doe'}], 'year': 2011})
@software{g.tst,
title = {GRASS GIS: g.tst module},
Expand All @@ -639,47 +667,106 @@ def print_bibtex(citation):
# TODO: make this an option to allow for software in case it is supported
entry_type = "misc"
key = remove_dots_from_module_name(citation["module"])
print("@", entry_type, "{", key, ",", sep="")
print("@", entry_type, "{", key, ",", sep="", file=output)

print(" title = {{", "GRASS GIS: ", citation["module"], " module}},", sep="")
print(
" title = {{",
"GRASS GIS: ",
citation["module"],
" module}},",
sep="",
file=output,
)

author_names = [author["name"] for author in citation["authors"]]
print(" author = {", " and ".join(author_names), "},", sep="")
print(" howpublished = {", citation["code-url"], "},", sep="")
print(" year = {", citation["year"], "}", sep="")
print(" note = {Accessed: ", citation["access"], "},", sep="")
print("}")
print(
" author = {",
" and ".join(author_names),
"},",
sep="",
file=output,
)
print(
" howpublished = {",
citation["code-url"],
"},",
sep="",
file=output,
)
print(" year = {", citation["year"], "}", sep="", file=output)
print(
" note = {Accessed: ",
citation["access"],
"},",
sep="",
file=output,
)
print("}", file=output)


def print_json(citation, output):
"""Create JSON dump from the citation dictionary
def print_json(citation):
"""Create JSON dump from the citation dictionary"""
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
"""
cleaned = remove_empty_values_from_dict(citation)
# since the format is already compact, let's make it even more
# compact by omitting the spaces after separators
print(json.dumps(cleaned, separators=(",", ":")))
print(json.dumps(cleaned, separators=(",", ":")), file=output)


def print_pretty_json(citation, output):
"""Create pretty-printed JSON dump from the citation dictionary
def print_pretty_json(citation):
"""Create pretty-printed JSON dump from the citation dictionary"""
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
"""
cleaned = remove_empty_values_from_dict(citation)
# the default separator for list items would leave space at the end
# of each line, so providing a custom one
# only small indent needed, so using 2
# sorting keys because only that can provide consistent output
print(json.dumps(cleaned, separators=(",", ": "), indent=2, sort_keys=True))
print(
json.dumps(
cleaned,
separators=(",", ": "),
indent=2,
sort_keys=True,
),
file=output,
)


def print_csl_json(citation):
"""Create pretty-printed CSL JSON from the citation dictionary"""
def print_csl_json(citation, output):
"""Create pretty-printed CSL JSON from the citation dictionary
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
"""
csl = internal_to_csl_json(citation)
# the default separator for list items would leave space at the end
# of each line, so providing a custom one
# only small indent needed, so using 2
# sorting keys because only that can provide consistent output
print(json.dumps(csl, separators=(",", ": "), indent=2, sort_keys=True))
print(
json.dumps(
csl,
separators=(",", ": "),
indent=2,
sort_keys=True,
),
file=output,
)


def print_chicago_footnote(citation, output):
"""Create chicago-footnote from the citation dictionary
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
"""

def print_chicago_footnote(citation):
num_authors = len(citation["authors"])
authors_text = ""
for i, author in enumerate(citation["authors"]):
Expand All @@ -692,18 +779,22 @@ def print_chicago_footnote(citation):
title = "GRASSS GIS module {}".format(citation["module"])
print(
"{authors_text}, {title} ({grass-version}), computer software"
" ({year}).".format(authors_text=authors_text, title=title, **citation)
" ({year}).".format(authors_text=authors_text, title=title, **citation),
file=output,
)


def print_plain(citation):
def print_plain(citation, output):
"""Create citation from dictionary as plain text
:param dict citation: module citation
:output_io.TextIOWrapper output: sys.stdout or text file stream
>>> print_plain({'module': 'g.tst', 'authors': [{'name': 'Joe Doe'}]})
GRASS GIS module g.tst
Joe Doe
"""
print("GRASS GIS module", citation["module"])
print("GRASS GIS module", citation["module"], file=output)
num_authors = len(citation["authors"])
authors_text = ""
for i, author in enumerate(citation["authors"]):
Expand All @@ -716,7 +807,7 @@ def print_plain(citation):
authors_text += " ({feature})".format(**author)
if i < num_authors - 1:
authors_text += "\n"
print(authors_text)
print(authors_text, file=output)


# private dict for format name to function call
Expand All @@ -729,12 +820,19 @@ def print_plain(citation):
"csl-json": print_csl_json,
"chicago-footnote": print_chicago_footnote,
"plain": print_plain,
"dict": lambda d: pprint(dict(d)), # only plain dict pretty prints
"dict": lambda d, output: pprint(
dict(d), stream=output
), # only plain dict pretty prints
}


def print_citation(citation, format):
"""Create citation from dictionary in a given format"""
def print_citation(citation, format, output):
"""Create citation from dictionary in a given format
:param dict citation: module citation
:param str format: citation format
:output_io.TextIOWrapper output: sys.stdout or text file stream
"""
# only catch the specific dict access, don't call the function

# funs with special handling of parameters first
Expand All @@ -748,7 +846,7 @@ def print_citation(citation, format):
function = _FORMAT_FUNCTION[format]
except KeyError:
raise RuntimeError(_("Unsupported format or style: %s" % format))
function(citation)
function(citation, output)


def grass_cff_reference(grass_version, scope=None):
Expand Down Expand Up @@ -842,23 +940,50 @@ def main(options, flags):
_("Option format=citeproc requires also" " the option style to be set")
)
vertical_separator = options["vertical_separator"]
output = options["output"]
if output:
try:
output = open(output, "w")
except FileNotFoundError:
gs.fatal(
_(
"No such file or directory '{output_file}'."
" Please choose correct output file path.".format(
output_file=output,
)
)
)
except PermissionError:
gs.fatal(
_(
"Permission denied '{output_file}'."
" Please change the permission of the output file"
" to allow writing.".format(
output_file=output,
)
)
)
else:
output = sys.stdout

error_count = 0
for name in names:
try:
citation = citation_for_module(name, add_grass=flags["d"])
if vertical_separator:
# TODO: decide if we want the newline here or not
print(vertical_separator)
print_citation(citation, output_format)
print(vertical_separator, file=output)
print_citation(citation, output_format, output)
except RuntimeError as error:
message = _("Module {name}: {error}".format(**locals()))
if flags["s"]:
gs.warning(message)
error_count += 1
continue
else:
output.close()
gs.fatal(message)
output.close()
if flags["s"] and len(names) > 1:
gs.warning(_("Errors in parsing {} modules").format(error_count))

Expand Down
Loading

0 comments on commit b0ee62f

Please sign in to comment.