Skip to content

Commit ee35164

Browse files
authored
R package generator improvements + async support (#1048)
* ✨ async/dynamic support in R pkg deps * 🔪 remove Authors block * 🔪 insert package version number * 🔨 use verbose 📦 title and desc from YAML * ✨ autodetect vignettes * check for author/maintainer address * ✋ halt processing if fatal errors found * autopopulate KeepSource * auto-escape % in docstrings * 🔪 filter examples from docstrings in R
1 parent 34b473a commit ee35164

File tree

1 file changed

+83
-9
lines changed

1 file changed

+83
-9
lines changed

dash/development/_r_components_generation.py

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@
4646
file = "deps"), meta = NULL,
4747
script = {script_name},
4848
stylesheet = {css_name}, head = NULL, attachment = NULL, package = "{rpkgname}",
49-
all_files = FALSE), class = "html_dependency")""" # noqa:E501
49+
all_files = FALSE{async_or_dynamic}), class = "html_dependency")""" # noqa:E501
5050

5151
frame_body_template = """`{project_shortname}` = structure(list(name = "{project_shortname}",
5252
version = "{project_ver}", src = list(href = NULL,
5353
file = "deps"), meta = NULL,
5454
script = {script_name},
5555
stylesheet = {css_name}, head = NULL, attachment = NULL, package = "{rpkgname}",
56-
all_files = FALSE), class = "html_dependency")""" # noqa:E501
56+
all_files = FALSE{async_or_dynamic}), class = "html_dependency")""" # noqa:E501
5757

5858
frame_close_template = """)
5959
return(deps_metadata)
@@ -81,9 +81,8 @@
8181
"""
8282

8383
description_template = """Package: {package_name}
84-
Title: {package_description}
84+
Title: {package_title}
8585
Version: {package_version}
86-
Authors @R: as.person(c({package_author}))
8786
Description: {package_description}
8887
Depends: R (>= 3.0.2){package_depends}
8988
Imports: {package_imports}
@@ -92,7 +91,8 @@
9291
URL: {package_url}
9392
BugReports: {package_issues}
9493
Encoding: UTF-8
95-
LazyData: true
94+
LazyData: true{vignette_builder}
95+
KeepSource: true
9696
Author: {package_author_no_email}
9797
Maintainer: {maintainer}
9898
"""
@@ -276,18 +276,23 @@ def generate_js_metadata(pkg_data, project_shortname):
276276
# pylint: disable=consider-using-enumerate
277277
if len(alldist) > 1:
278278
for dep in range(len(alldist)):
279-
rpp = alldist[dep]["relative_package_path"]
279+
curr_dep = alldist[dep]
280+
rpp = curr_dep["relative_package_path"]
281+
282+
async_or_dynamic = get_async_type(curr_dep)
283+
280284
if "dash_" in rpp:
281285
dep_name = rpp.split(".")[0]
282286
else:
283287
dep_name = "{}".format(project_shortname)
284-
project_ver = str(dep)
288+
285289
if "css" in rpp:
286290
css_name = "'{}'".format(rpp)
287291
script_name = 'NULL'
288292
else:
289293
script_name = "'{}'".format(rpp)
290294
css_name = 'NULL'
295+
291296
function_frame += [
292297
frame_element_template.format(
293298
dep_name=dep_name,
@@ -296,23 +301,30 @@ def generate_js_metadata(pkg_data, project_shortname):
296301
project_shortname=project_shortname,
297302
script_name=script_name,
298303
css_name=css_name,
304+
async_or_dynamic=async_or_dynamic,
299305
)
300306
]
301307
function_frame_body = ",\n".join(function_frame)
302308
elif len(alldist) == 1:
303-
rpp = alldist[0]["relative_package_path"]
309+
dep = alldist[0]
310+
rpp = dep["relative_package_path"]
311+
312+
async_or_dynamic = get_async_type(dep)
313+
304314
if "css" in rpp:
305315
css_name = "'{}'".format(rpp)
306316
script_name = "NULL"
307317
else:
308318
script_name = "'{}'".format(rpp)
309319
css_name = "NULL"
320+
310321
function_frame_body = frame_body_template.format(
311322
project_shortname=project_shortname,
312323
project_ver=project_ver,
313324
rpkgname=rpkgname,
314325
script_name=script_name,
315326
css_name=css_name,
327+
async_or_dynamic=async_or_dynamic,
316328
)
317329

318330
function_string = "".join(
@@ -322,6 +334,24 @@ def generate_js_metadata(pkg_data, project_shortname):
322334
return function_string
323335

324336

337+
# determine whether dependency uses async or dynamic flag
338+
# then return the properly formatted string if so, i.e.
339+
# " async = TRUE,". a dependency can have async or
340+
# dynamic elements, neither of these, but never both.
341+
def get_async_type(dep):
342+
async_or_dynamic = ""
343+
for key in dep.keys():
344+
if (key in ['async', 'dynamic']):
345+
keyval = dep[key]
346+
if not isinstance(keyval, bool):
347+
keyval = "'{}'".format(keyval.lower())
348+
else:
349+
keyval = str(keyval).upper()
350+
async_or_dynamic = \
351+
", {} = {}".format(key, keyval)
352+
return async_or_dynamic
353+
354+
325355
# This method wraps code within arbitrary LaTeX-like tags, which are used
326356
# by R's internal help parser for constructing man pages
327357
def wrap(tag, code):
@@ -369,6 +399,15 @@ def write_help_file(name, props, description, prefix, rpkg_data):
369399
for p in prop_keys
370400
)
371401

402+
# auto-replace any unescaped backslashes for compatibility with R docs
403+
description = re.sub(r"(?<!\\)%", "\\%", description)
404+
item_text = re.sub(r"(?<!\\)%", "\\%", item_text)
405+
406+
# scrub examples which begin with **Example Usage**, as these should be
407+
# provided as R code within dash-info.yaml
408+
if "**Example Usage**" in description:
409+
description = description.split("**Example Usage**")[0].rstrip()
410+
372411
if any(key.endswith("-*") for key in prop_keys):
373412
default_argtext += ', ...'
374413
item_text += wildcard_help_template.format(get_wildcards_r(prop_keys))
@@ -400,6 +439,7 @@ def write_help_file(name, props, description, prefix, rpkg_data):
400439
fa.write(result + '\n')
401440

402441

442+
# pylint: disable=too-many-arguments
403443
def write_class_file(name,
404444
props,
405445
description,
@@ -509,7 +549,21 @@ def generate_rpkg(
509549

510550
package_name = snake_case_to_camel_case(project_shortname)
511551
lib_name = pkg_data.get("name")
512-
package_description = pkg_data.get("description", "")
552+
553+
if rpkg_data is not None:
554+
if rpkg_data.get("pkg_help_title"):
555+
package_title = rpkg_data.get("pkg_help_title",
556+
pkg_data.get("description",
557+
""))
558+
if rpkg_data.get("pkg_help_description"):
559+
package_description = rpkg_data.get("pkg_help_description",
560+
pkg_data.get("description",
561+
""))
562+
else:
563+
# fall back to using description in package.json, if present
564+
package_title = pkg_data.get("description", "")
565+
package_description = pkg_data.get("description", "")
566+
513567
package_version = pkg_data.get("version", "0.0.1")
514568

515569
# remove leading and trailing commas
@@ -547,6 +601,16 @@ def generate_rpkg(
547601

548602
maintainer = pkg_data.get("maintainer", pkg_data.get("author"))
549603

604+
if "<" not in package_author or "<" not in maintainer:
605+
print(
606+
"Error, aborting R package generation: "
607+
"R packages require a properly formatted author or "
608+
"maintainer field or installation will fail. Please include "
609+
"an email address enclosed within < > brackets in package.json. ",
610+
file=sys.stderr,
611+
)
612+
sys.exit(1)
613+
550614
if not (os.path.isfile("LICENSE") or os.path.isfile("LICENSE.txt")):
551615
package_license = pkg_data.get("license", "")
552616
else:
@@ -565,6 +629,14 @@ def generate_rpkg(
565629
for rpackage in rpackage_list:
566630
packages_string += "\nimport({})\n".format(rpackage)
567631

632+
if os.path.exists("vignettes"):
633+
vignette_builder = "VignetteBuilder: knitr\n"
634+
if "knitr" not in package_suggests and \
635+
"rmarkdown" not in package_suggests:
636+
package_suggests += ", knitr, rmarkdown".lstrip(", ")
637+
else:
638+
vignette_builder = ""
639+
568640
pkghelp_stub_path = os.path.join("man", package_name + "-package.Rd")
569641

570642
# generate the internal (not exported to the user) functions which
@@ -582,6 +654,7 @@ def generate_rpkg(
582654

583655
description_string = description_template.format(
584656
package_name=package_name,
657+
package_title=package_title,
585658
package_description=package_description,
586659
package_version=package_version,
587660
package_author=package_author,
@@ -591,6 +664,7 @@ def generate_rpkg(
591664
package_license=package_license,
592665
package_url=package_url,
593666
package_issues=package_issues,
667+
vignette_builder=vignette_builder,
594668
package_author_no_email=package_author_no_email,
595669
maintainer=maintainer,
596670
)

0 commit comments

Comments
 (0)