From 03b069d06d263cdc559b067a93c29cb22433a598 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 5 Dec 2018 00:19:55 -0500 Subject: [PATCH 01/55] refactored code to work with generator method --- dash/development/_all_keywords.py | 30 +- dash/development/_py_components_generation.py | 4 +- dash/development/_r_components_generation.py | 541 ++++++++++++++++++ dash/development/component_generator.py | 73 ++- 4 files changed, 641 insertions(+), 7 deletions(-) create mode 100644 dash/development/_r_components_generation.py diff --git a/dash/development/_all_keywords.py b/dash/development/_all_keywords.py index 76842b56af..550c1d9fa0 100644 --- a/dash/development/_all_keywords.py +++ b/dash/development/_all_keywords.py @@ -3,7 +3,7 @@ # >>> import keyword # >>> keyword.kwlist -kwlist = set([ +python_keywords = set([ 'and', 'elif', 'is', @@ -42,3 +42,31 @@ 'def', 'lambda' ]) + +# This is a set of R reserved words that cannot be used as function argument names. +# +# Reserved words can be obtained from R's help pages by executing the statement below: +# > ?reserved + +r_keywords = set([ + 'if', + 'else', + 'repeat', + 'while', + 'function', + 'for', + 'in', + 'next', + 'break', + 'TRUE', + 'FALSE', + 'NULL', + 'Inf', + 'NaN', + 'NA', + 'NA_integer_', + 'NA_real_', + 'NA_complex_', + 'NA_character_', + '...' +]) diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index ba17ccd84d..d187c3a0e9 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -4,7 +4,7 @@ import textwrap from dash.development.base_component import _explicitize_args -from ._all_keywords import kwlist +from ._all_keywords import python_keywords from .base_component import Component @@ -122,7 +122,7 @@ def __repr__(self): '{:s}=Component.UNDEFINED'.format(p)) for p in prop_keys if not p.endswith("-*") and - p not in kwlist and + p not in python_keywords and p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs'] ) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py new file mode 100644 index 0000000000..899488918a --- /dev/null +++ b/dash/development/_r_components_generation.py @@ -0,0 +1,541 @@ +from __future__ import absolute_import + +import os +import sys + +from ._all_keywords import r_keywords +from ._py_components_generation import reorder_props + + +# This is an initial attempt at resolving type inconsistencies +# between R and JSON. +def json_to_r_type(current_prop): + object_type = current_prop['type'].values() + if 'defaultValue' in current_prop and object_type == ['string']: + if current_prop['defaultValue']['value'].__contains__('\''): + argument = current_prop['defaultValue']['value'] + else: + argument = "\'{:s}\'".format(current_prop['defaultValue']['value']) + elif 'defaultValue' in current_prop and object_type == ['object']: + argument = 'list()' + elif 'defaultValue' in current_prop and \ + current_prop['defaultValue']['value'] == '[]': + argument = 'list()' + else: + argument = 'NULL' + return argument + + +# pylint: disable=R0914 +def generate_class_string_r(name, props, project_shortname, prefix): + """ + Dynamically generate class strings to have nicely formatted documentation, + and function arguments + + Inspired by http://jameso.be/2013/08/06/namedtuple.html + + Parameters + ---------- + name + props + project_shortname + prefix + + Returns + ------- + string + + """ + + c = '''{prefix}{name} <- function(..., {default_argtext}) {{ + + component <- list( + props = list({default_paramtext}), + type = '{name}', + project_shortname = '{project_shortname}', + propNames = c({prop_names}), + package = '{package_name}' + ) + + component$props <- filter_null(component$props) + component <- append_wildcard_props(component, wildcards = {default_wildcards}, ...) + + structure(component, class = c('dash_component', 'list')) +}}''' + + # Here we convert from snake case to camel case + package_name = make_package_name_r(project_shortname) + + prop_keys = list(props.keys()) + + default_paramtext = '' + default_argtext = '' + default_wildcards = '' + + # Produce a string with all property names other than WCs + prop_names = ", ".join( + ('\'{:s}\''.format(p)) + for p in prop_keys + if '*' not in p and + p not in ['setProps', 'dashEvents', 'fireEvent'] + ) + + # in R, we set parameters with no defaults to NULL + # Here we'll do that if no default value exists + default_wildcards += ", ".join( + ('\'{:s}\''.format(p)) + for p in prop_keys + if '*' in p + ) + + if default_wildcards == '': + default_wildcards = 'NULL' + else: + default_wildcards = 'c({:s})'.format(default_wildcards) + + default_argtext += ", ".join( + ('{:s}={}'.format(p, json_to_r_type(props[p])) + if 'defaultValue' in props[p] else + '{:s}=NULL'.format(p)) + for p in prop_keys + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] + ) + + if 'children' in props: + prop_keys.remove('children') + + # pylint: disable=C0301 + default_paramtext += ", ".join( + ('{:s}={:s}'.format(p, p) + if p != "children" else + '{:s}=c(children, assert_valid_children(..., wildcards = {:s}))'.format(p, default_wildcards)) + for p in props.keys() + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] + ) + return c.format(prefix=prefix, + name=name, + default_argtext=default_argtext, + default_paramtext=default_paramtext, + project_shortname=project_shortname, + prop_names=prop_names, + package_name=package_name, + default_wildcards=default_wildcards) + + +# pylint: disable=R0914 +def generate_js_metadata_r(project_shortname): + """ + Dynamically generate R function to supply JavaScript + dependency information required by htmltools package, + which is loaded by dashR. + + Inspired by http://jameso.be/2013/08/06/namedtuple.html + + Parameters + ---------- + project_shortname + + Returns + ------- + function_string + """ + + # import component library module into sys + mod = sys.modules[project_shortname] + + jsdist = getattr(mod, '_js_dist', []) + project_ver = getattr(mod, '__version__', []) + + rpkgname = make_package_name_r(project_shortname) + + # since _js_dist may suggest more than one dependency, need + # a way to iterate over all dependencies for a given set. + # here we define an opening, element, and closing string -- + # if the total number of dependencies > 1, we can concatenate + # them and write a list object in R with multiple elements + function_frame_open = '''.{rpkgname}_js_metadata <- function() {{ +deps_metadata <- list( +'''.format(rpkgname=rpkgname) + + function_frame = [] + + # the following string represents all the elements in an object + # of the html_dependency class, which will be propagated by + # iterating over _js_dist in __init__.py + function_frame_element = '''`{dep_name}` = structure(list(name = "{dep_name}", +version = "{project_ver}", src = list(href = NULL, +file = "lib/"), meta = NULL, +script = "{dep_rpp}", +stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", +all_files = FALSE), class = "html_dependency")''' + + if len(jsdist) > 1: + for dep in range(len(jsdist)): + if jsdist[dep]['relative_package_path'].__contains__('dash_'): + dep_name = jsdist[dep]['relative_package_path'].split('.')[0] + else: + dep_name = '{:s}_{:s}'.format(project_shortname, str(dep)) + project_ver = str(dep) + function_frame += [function_frame_element.format(dep_name=dep_name, + project_ver=project_ver, + rpkgname=rpkgname, + project_shortname=project_shortname, + dep_rpp=jsdist[dep]['relative_package_path']) + ] + function_frame_body = ',\n'.join(function_frame) + elif len(jsdist) == 1: + function_frame_body = '''`{project_shortname}` = structure(list(name = "{project_shortname}", +version = "{project_ver}", src = list(href = NULL, +file = "lib/"), meta = NULL, +script = "{dep_rpp}", +stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", +all_files = FALSE), class = "html_dependency")'''.format(project_shortname=project_shortname, + project_ver=project_ver, + rpkgname=rpkgname, + dep_rpp=jsdist[0]['relative_package_path']) + + function_frame_close = ''') + return(deps_metadata) +}''' + + function_string = ''.join([function_frame_open, + function_frame_body, + function_frame_close] + ) + + return function_string + + +def write_help_file_r(name, props, description, prefix): + """ + Write R documentation file (.Rd) given component name and properties + + Parameters + ---------- + name + props + description + prefix + + Returns + ------- + + + """ + file_name = '{:s}{:s}.Rd'.format(prefix, name) + prop_keys = list(props.keys()) + + default_argtext = '' + item_text = '' + + # Ensure props are ordered with children first + props = reorder_props(props=props) + + default_argtext += ", ".join( + ('{:s}={}'.format(p, json_to_r_type(props[p])) + if 'defaultValue' in props[p] else + '{:s}=NULL'.format(p)) + for p in prop_keys + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] + ) + + item_text += "\n\n".join( + ('\\item{{{:s}}}{{{:s}}}'.format(p, props[p]['description'])) + for p in prop_keys + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] + ) + + help_string = '''% Auto-generated: do not edit by hand +\\name{{{prefix}{name}}} +\\alias{{{prefix}{name}}} +\\title{{{name} component}} +\\usage{{ +{prefix}{name}(..., {default_argtext}) +}} +\\arguments{{ +{item_text} +}} +\\description{{ +{description} +}} + ''' + if not os.path.exists('man'): + os.makedirs('man') + + file_path = os.path.join('man', file_name) + with open(file_path, 'w') as f: + f.write(help_string.format( + prefix=prefix, + name=name, + default_argtext=default_argtext, + item_text=item_text, + description=description.replace('\n', ' ') + )) + + +def write_class_file_r(name, props, description, project_shortname, prefix=None): + """ + Generate a R class file (.R) given a class string + + Parameters + ---------- + name + props + description + project_shortname + prefix + + Returns + ------- + + """ + + import_string =\ + "# AUTO GENERATED FILE - DO NOT EDIT\n\n" + class_string = generate_class_string_r( + name, + props, + project_shortname, + prefix + ) + file_name = "{:s}{:s}.R".format(prefix, name) + + if not os.path.exists('R'): + os.makedirs('R') + + file_path = os.path.join('R', file_name) + with open(file_path, 'w') as f: + f.write(import_string) + f.write(class_string) + + # generate the internal (not exported to the user) functions which + # supply the JavaScript dependencies to the htmlDependency package, + # which is required by DashR (this avoids having to generate an + # RData file from within Python, given the current package generation + # workflow) + write_js_metadata_r( + project_shortname + ) + + # generate the R help pages for each of the Dash components that we + # are transpiling -- this is done to avoid using Roxygen2 syntax, + # we may eventually be able to generate similar documentation using + # doxygen and an R plugin, but for now we'll just do it on our own + # from within Python + write_help_file_r( + name, + props, + description, + prefix + ) + + +# pylint: disable=unused-variable +def generate_export_string_r(name, prefix): + if not name.endswith('-*') and \ + str(name) not in r_keywords and \ + str(name) not in ['setProps', 'children', 'dashEvents']: + return 'export({:s}{:s})\n'.format(prefix, name) + + +def write_js_metadata_r(project_shortname): + """ + Write an internal (not exported) function to return all JS + dependencies as required by htmlDependency package given a + function string + + Parameters + ---------- + project_shortname + + Returns + ------- + + """ + function_string = generate_js_metadata_r( + project_shortname + ) + file_name = "internal.R" + + # the R source directory for the package won't exist on first call + # create the R directory if it is missing + if not os.path.exists('R'): + os.makedirs('R') + + file_path = os.path.join('R', file_name) + with open(file_path, 'w') as f: + f.write(function_string) + + # now copy over all JS dependencies from the (Python) components dir + import shutil + import glob + + # the inst/lib directory for the package won't exist on first call + # create this directory if it is missing + if not os.path.exists('inst'): + os.makedirs('inst') + + if not os.path.exists('inst/lib'): + os.makedirs('inst/lib') + + for javascript in glob.glob('{}/*.js'.format(project_shortname)): + shutil.copy(javascript, 'inst/lib/') + + +# pylint: disable=R0914 +def generate_rpkg(pkg_data, + project_shortname, + export_string): + """ + Generate documents for R package creation + + Parameters + ---------- + pkg_data + project_shortname + export_string + + Returns + ------- + + """ + # Leverage package.json to import specifics which are also applicable + # to R package that we're generating here + package_name = make_package_name_r(project_shortname) + package_description = pkg_data['description'] + package_version = pkg_data['version'] + package_issues = pkg_data['bugs']['url'] + package_url = pkg_data['homepage'] + + package_author = pkg_data['author'] + + package_author_no_email = package_author.split(" <")[0] + ' [aut]' + + if not (os.path.isfile('LICENSE') or os.path.isfile('LICENSE.txt')): + package_license = pkg_data['license'] + else: + package_license = pkg_data['license'] + ' + file LICENSE' + # R requires that the LICENSE.txt file be named LICENSE + if not os.path.isfile('LICENSE'): + os.symlink("LICENSE.txt", "LICENSE") + + import_string =\ + '# AUTO GENERATED FILE - DO NOT EDIT\n\n' + + description_string = \ + '''Package: {package_name} +Title: {package_description} +Version: {package_version} +Authors @R: as.person(c({package_author})) +Description: {package_description} +Suggests: testthat, roxygen2 +License: {package_license} +URL: {package_url} +BugReports: {package_issues} +Encoding: UTF-8 +LazyData: true +Author: {package_author_no_email} +Maintainer: {package_author} +''' + + description_string = description_string.format(package_name=package_name, + package_description=package_description, + package_version=package_version, + package_author=package_author, + package_license=package_license, + package_url=package_url, + package_issues=package_issues, + package_author_no_email=package_author_no_email) + + rbuild_ignore_string = r'''# ignore JS config files/folders +node_modules/ +coverage/ +src/ +lib/ +.babelrc +.builderrc +.eslintrc +.npmignore + +# demo folder has special meaning in R +# this should hopefully make it still +# allow for the possibility to make R demos +demo/*.js +demo/*.html +demo/*.css + +# ignore python files/folders +setup.py +usage.py +setup.py +requirements.txt +MANIFEST.in +CHANGELOG.md +test/ +# CRAN has weird LICENSE requirements +LICENSE.txt +^.*\.Rproj$ +^\.Rproj\.user$ +''' + + with open('NAMESPACE', 'w') as f: + f.write(import_string) + f.write(export_string) + + with open('DESCRIPTION', 'w') as f2: + f2.write(description_string) + + with open('.Rbuildignore', 'w') as f3: + f3.write(rbuild_ignore_string) + + +# This converts a string from snake case to camel case +# Not required for R package name to be in camel case, +# but probably more conventional this way +def make_package_name_r(namestring): + first, rest = namestring.split('_')[0], namestring.split('_')[1:] + return first + ''.join(word.capitalize() for word in rest) + + +# For the R packages corresponding to the component +# libraries, use 'project_shortname' to determine +# the prefix for the set of R functions we're +# generating that correspond to Dash components. +# +# e.g. the graph component in DCC will be prepended +# with 'core', so that the R function is coreGraph() +def get_shortname_prefix(project_shortname): + if project_shortname == 'dash_html_components': + prefix = 'html' + elif project_shortname == 'dash_core_components': + prefix = 'core' + else: + prefix = '' + + return prefix + +# pylint: disable=unused-variable +def generate_exports_r(project_shortname, components, metadata, pkg_data, prefix, **kwargs): + export_string = '' + for component in components: + if not component.endswith('-*') and \ + str(component) not in r_keywords and \ + str(component) not in ['setProps', 'children', 'dashEvents']: + export_string += 'export({:s}{:s})\n'.format(prefix, component) + + # now, bundle up the package information and create all the requisite + # elements of an R package, so that the end result is installable either + # locally or directly from GitHub + generate_rpkg( + pkg_data, + project_shortname, + export_string + ) \ No newline at end of file diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 8e04e0eab4..2ce999f84b 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -7,14 +7,18 @@ import os import argparse import shutil +import functools import pkg_resources +from .component_loader import _get_metadata +from ._r_components_generation import write_class_file_r +from ._r_components_generation import generate_exports_r +from ._r_components_generation import get_shortname_prefix from ._py_components_generation import generate_class_file from ._py_components_generation import generate_imports from ._py_components_generation import generate_classes_files - class _CombinedFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass @@ -22,7 +26,17 @@ class _CombinedFormatter(argparse.ArgumentDefaultsHelpFormatter, # pylint: disable=too-many-locals def generate_components(components_source, project_shortname, - package_info_filename='package.json'): + package_info_filename='package.json', + generate_r_components=False): + project_shortname = project_shortname.replace('-', '_').rstrip('/\\') + + import importlib + + # import component library module + importlib.import_module(project_shortname) + + prefix = get_shortname_prefix(project_shortname) + is_windows = sys.platform == 'win32' extract_path = pkg_resources.resource_filename('dash', 'extract-meta.js') @@ -52,11 +66,18 @@ def generate_components(components_source, project_shortname, sys.exit(1) metadata = json.loads(out.decode()) + generator_methods = [generate_class_file] + # pkg_generator_methods = [generate_imports] + + if generate_r_components: + generator_methods.append( + functools.partial(write_class_file_r, prefix=prefix)) + #generator_methods.append(generate_rpkg) components = generate_classes_files( project_shortname, metadata, - generate_class_file + *generator_methods ) with open(os.path.join(project_shortname, 'metadata.json'), 'w') as f: @@ -64,6 +85,44 @@ def generate_components(components_source, project_shortname, generate_imports(project_shortname, components) + if generate_r_components: + # -- do all the R stuff here, remove loop as it is unnecessary + # Remove the R NAMESPACE file if it exists, this will be repopulated + # if os.path.isfile('NAMESPACE'): + # os.remove('NAMESPACE') + + with open('package.json', 'r') as f: + pkg_data = json.load(f) + + generate_exports_r(project_shortname, components, metadata, pkg_data, prefix) + + +def generate_components_r(namespace, + metadata_path='lib/metadata.json', + pkgjson_path='package.json'): + """Load React component metadata into a format Dash can parse, + then create R files for component loading. + + Usage: generate_classes_r() + + Keyword arguments: + namespace -- name of the generated python package (also output dir) + + metadata_path -- a path to a JSON file created by + [`react-docgen`](https://github.com/reactjs/react-docgen). + + pkgjson_path -- a path to a JSON file created by + [`cookiecutter`](https://github.com/audreyr/cookiecutter). + + Returns: + """ + + data = _get_metadata(metadata_path) + pkg_data = _get_metadata(pkgjson_path) + export_string = '' + + + def cli(): parser = argparse.ArgumentParser( @@ -83,10 +142,16 @@ def cli(): default='package.json', help='The filename of the copied `package.json` to `project_shortname`' ) + parser.add_argument( + '-r', '--rlang', + action='store_true', + help='Generate Dash components for R, and package for installation.' + ) args = parser.parse_args() generate_components(args.components_source, args.project_shortname, - package_info_filename=args.package_info_filename) + package_info_filename=args.package_info_filename, + generate_r_components=args.rlang) if __name__ == '__main__': From 9b10f9114e7691559399c678c999e19f68e254d0 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 5 Dec 2018 11:53:43 -0500 Subject: [PATCH 02/55] fixed bug in component generation --- dash/development/_r_components_generation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 899488918a..3cc6b089ba 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -52,7 +52,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): component <- list( props = list({default_paramtext}), type = '{name}', - project_shortname = '{project_shortname}', + namespace = '{project_shortname}', propNames = c({prop_names}), package = '{package_name}' ) @@ -337,6 +337,8 @@ def write_class_file_r(name, props, description, project_shortname, prefix=None) prefix ) + print('Generated {}'.format(file_name)) + # pylint: disable=unused-variable def generate_export_string_r(name, prefix): From 670a03fdb769ab2a17cb3f4cc66d7ca0fe4b8519 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Thu, 6 Dec 2018 17:41:00 -0500 Subject: [PATCH 03/55] fixed bug in R help file function descriptions --- dash/development/_r_components_generation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 3cc6b089ba..f2070c0319 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -257,14 +257,14 @@ def write_help_file_r(name, props, description, prefix): \\name{{{prefix}{name}}} \\alias{{{prefix}{name}}} \\title{{{name} component}} +\\description{{ +{description} +}} \\usage{{ {prefix}{name}(..., {default_argtext}) }} \\arguments{{ {item_text} -}} -\\description{{ -{description} }} ''' if not os.path.exists('man'): From 27aeea782bf8e3b5d00efabf1ba4ba8ab4e8d3d2 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Fri, 7 Dec 2018 23:29:03 -0500 Subject: [PATCH 04/55] edits to resolve pylint and flake8 unhappiness --- dash/development/_all_keywords.py | 79 +++--------- dash/development/_r_components_generation.py | 121 +++++++++++-------- dash/development/component_generator.py | 35 +----- 3 files changed, 90 insertions(+), 145 deletions(-) diff --git a/dash/development/_all_keywords.py b/dash/development/_all_keywords.py index 550c1d9fa0..a7498141b9 100644 --- a/dash/development/_all_keywords.py +++ b/dash/development/_all_keywords.py @@ -3,70 +3,23 @@ # >>> import keyword # >>> keyword.kwlist -python_keywords = set([ - 'and', - 'elif', - 'is', - 'global', - 'as', - 'in', - 'if', - 'from', - 'raise', - 'for', - 'except', - 'nonlocal', - 'pass', - 'finally', - 'print', - 'import', - 'True', - 'None', - 'return', - 'exec', - 'await', - 'else', - 'break', - 'not', - 'with', - 'class', - 'assert', - 'False', - 'yield', - 'try', - 'while', - 'continue', - 'del', - 'async', - 'or', - 'def', - 'lambda' -]) +python_keywords = { + 'and', 'elif', 'is', 'global', 'as', 'in', 'if', 'from', 'raise', 'for', + 'except', 'nonlocal', 'pass', 'finally', 'print', 'import', 'True', 'None', + 'return', 'exec', 'await', 'else', 'break', 'not', 'with', 'class', + 'assert', 'False', 'yield', 'try', 'while', 'continue', 'del', 'async', + 'or', 'def', 'lambda' +} -# This is a set of R reserved words that cannot be used as function argument names. +# This is a set of R reserved words that cannot be used as function +# argument names. # -# Reserved words can be obtained from R's help pages by executing the statement below: +# Reserved words can be obtained from R's help pages by executing the +# statement below: # > ?reserved -r_keywords = set([ - 'if', - 'else', - 'repeat', - 'while', - 'function', - 'for', - 'in', - 'next', - 'break', - 'TRUE', - 'FALSE', - 'NULL', - 'Inf', - 'NaN', - 'NA', - 'NA_integer_', - 'NA_real_', - 'NA_complex_', - 'NA_character_', - '...' -]) +r_keywords = { + 'if', 'else', 'repeat', 'while', 'function', 'for', 'in', 'next', 'break', + 'TRUE', 'FALSE', 'NULL', 'Inf', 'NaN', 'NA', 'NA_integer_', 'NA_real_', + 'NA_complex_', 'NA_character_', '...' +} diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index f2070c0319..edaa16bcc6 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -61,7 +61,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): component <- append_wildcard_props(component, wildcards = {default_wildcards}, ...) structure(component, class = c('dash_component', 'list')) -}}''' +}}''' # noqa:E501 # Here we convert from snake case to camel case package_name = make_package_name_r(project_shortname) @@ -84,8 +84,8 @@ def generate_class_string_r(name, props, project_shortname, prefix): # Here we'll do that if no default value exists default_wildcards += ", ".join( ('\'{:s}\''.format(p)) - for p in prop_keys - if '*' in p + for p in prop_keys + if '*' in p ) if default_wildcards == '': @@ -95,12 +95,12 @@ def generate_class_string_r(name, props, project_shortname, prefix): default_argtext += ", ".join( ('{:s}={}'.format(p, json_to_r_type(props[p])) - if 'defaultValue' in props[p] else - '{:s}=NULL'.format(p)) - for p in prop_keys - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] + if 'defaultValue' in props[p] else + '{:s}=NULL'.format(p)) + for p in prop_keys + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] ) if 'children' in props: @@ -109,12 +109,13 @@ def generate_class_string_r(name, props, project_shortname, prefix): # pylint: disable=C0301 default_paramtext += ", ".join( ('{:s}={:s}'.format(p, p) - if p != "children" else - '{:s}=c(children, assert_valid_children(..., wildcards = {:s}))'.format(p, default_wildcards)) - for p in props.keys() - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] + if p != "children" else + '{:s}=c(children, assert_valid_children(..., wildcards = {:s}))' + .format(p, default_wildcards)) + for p in props.keys() + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] ) return c.format(prefix=prefix, name=name, @@ -173,6 +174,7 @@ def generate_js_metadata_r(project_shortname): stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", all_files = FALSE), class = "html_dependency")''' + # pylint: disable=consider-using-enumerate if len(jsdist) > 1: for dep in range(len(jsdist)): if jsdist[dep]['relative_package_path'].__contains__('dash_'): @@ -180,32 +182,37 @@ def generate_js_metadata_r(project_shortname): else: dep_name = '{:s}_{:s}'.format(project_shortname, str(dep)) project_ver = str(dep) - function_frame += [function_frame_element.format(dep_name=dep_name, - project_ver=project_ver, - rpkgname=rpkgname, - project_shortname=project_shortname, - dep_rpp=jsdist[dep]['relative_package_path']) - ] + function_frame += [function_frame_element.format( + dep_name=dep_name, + project_ver=project_ver, + rpkgname=rpkgname, + project_shortname=project_shortname, + dep_rpp=jsdist[dep]['relative_package_path'] + ) + ] function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: + # pylint: disable=line-too-long function_frame_body = '''`{project_shortname}` = structure(list(name = "{project_shortname}", version = "{project_ver}", src = list(href = NULL, file = "lib/"), meta = NULL, script = "{dep_rpp}", stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", -all_files = FALSE), class = "html_dependency")'''.format(project_shortname=project_shortname, - project_ver=project_ver, - rpkgname=rpkgname, - dep_rpp=jsdist[0]['relative_package_path']) +all_files = FALSE), class = "html_dependency")'''.\ + format(project_shortname=project_shortname, + project_ver=project_ver, + rpkgname=rpkgname, + dep_rpp=jsdist[0]['relative_package_path'] + ) function_frame_close = ''') - return(deps_metadata) +return(deps_metadata) }''' function_string = ''.join([function_frame_open, - function_frame_body, - function_frame_close] - ) + function_frame_body, + function_frame_close] + ) return function_string @@ -236,13 +243,13 @@ def write_help_file_r(name, props, description, prefix): props = reorder_props(props=props) default_argtext += ", ".join( - ('{:s}={}'.format(p, json_to_r_type(props[p])) - if 'defaultValue' in props[p] else - '{:s}=NULL'.format(p)) - for p in prop_keys - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] + '{:s}={}'.format(p, json_to_r_type(props[p])) + if 'defaultValue' in props[p] else + '{:s}=NULL'.format(p) + for p in prop_keys + if not p.endswith("-*") and + p not in r_keywords and + p not in ['setProps', 'dashEvents', 'fireEvent'] ) item_text += "\n\n".join( @@ -281,7 +288,11 @@ def write_help_file_r(name, props, description, prefix): )) -def write_class_file_r(name, props, description, project_shortname, prefix=None): +def write_class_file_r(name, + props, + description, + project_shortname, + prefix=None): """ Generate a R class file (.R) given a class string @@ -330,6 +341,7 @@ def write_class_file_r(name, props, description, project_shortname, prefix=None) # we may eventually be able to generate similar documentation using # doxygen and an R plugin, but for now we'll just do it on our own # from within Python + # noqa E344 write_help_file_r( name, props, @@ -340,7 +352,7 @@ def write_class_file_r(name, props, description, project_shortname, prefix=None) print('Generated {}'.format(file_name)) -# pylint: disable=unused-variable +# pylint: disable=inconsistent-return-statements def generate_export_string_r(name, prefix): if not name.endswith('-*') and \ str(name) not in r_keywords and \ @@ -432,8 +444,7 @@ def generate_rpkg(pkg_data, import_string =\ '# AUTO GENERATED FILE - DO NOT EDIT\n\n' - description_string = \ - '''Package: {package_name} + description_string = '''Package: {package_name} Title: {package_description} Version: {package_version} Authors @R: as.person(c({package_author})) @@ -448,14 +459,16 @@ def generate_rpkg(pkg_data, Maintainer: {package_author} ''' - description_string = description_string.format(package_name=package_name, - package_description=package_description, - package_version=package_version, - package_author=package_author, - package_license=package_license, - package_url=package_url, - package_issues=package_issues, - package_author_no_email=package_author_no_email) + description_string = description_string.format( + package_name=package_name, + package_description=package_description, + package_version=package_version, + package_author=package_author, + package_license=package_license, + package_url=package_url, + package_issues=package_issues, + package_author_no_email=package_author_no_email + ) rbuild_ignore_string = r'''# ignore JS config files/folders node_modules/ @@ -524,8 +537,14 @@ def get_shortname_prefix(project_shortname): return prefix -# pylint: disable=unused-variable -def generate_exports_r(project_shortname, components, metadata, pkg_data, prefix, **kwargs): + +# pylint: disable=unused-argument +def generate_exports_r(project_shortname, + components, + metadata, + pkg_data, + prefix, + **kwargs): export_string = '' for component in components: if not component.endswith('-*') and \ @@ -540,4 +559,4 @@ def generate_exports_r(project_shortname, components, metadata, pkg_data, prefix pkg_data, project_shortname, export_string - ) \ No newline at end of file + ) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 2ce999f84b..a044a3e499 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -10,7 +10,6 @@ import functools import pkg_resources -from .component_loader import _get_metadata from ._r_components_generation import write_class_file_r from ._r_components_generation import generate_exports_r @@ -19,6 +18,7 @@ from ._py_components_generation import generate_imports from ._py_components_generation import generate_classes_files + class _CombinedFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass @@ -67,12 +67,10 @@ def generate_components(components_source, project_shortname, metadata = json.loads(out.decode()) generator_methods = [generate_class_file] - # pkg_generator_methods = [generate_imports] if generate_r_components: generator_methods.append( functools.partial(write_class_file_r, prefix=prefix)) - #generator_methods.append(generate_rpkg) components = generate_classes_files( project_shortname, @@ -94,34 +92,9 @@ def generate_components(components_source, project_shortname, with open('package.json', 'r') as f: pkg_data = json.load(f) - generate_exports_r(project_shortname, components, metadata, pkg_data, prefix) - - -def generate_components_r(namespace, - metadata_path='lib/metadata.json', - pkgjson_path='package.json'): - """Load React component metadata into a format Dash can parse, - then create R files for component loading. - - Usage: generate_classes_r() - - Keyword arguments: - namespace -- name of the generated python package (also output dir) - - metadata_path -- a path to a JSON file created by - [`react-docgen`](https://github.com/reactjs/react-docgen). - - pkgjson_path -- a path to a JSON file created by - [`cookiecutter`](https://github.com/audreyr/cookiecutter). - - Returns: - """ - - data = _get_metadata(metadata_path) - pkg_data = _get_metadata(pkgjson_path) - export_string = '' - - + generate_exports_r( + project_shortname, components, metadata, pkg_data, prefix + ) def cli(): From 3aca86f3cafbc066343295dc2ad94a12a61e6ceb Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Fri, 7 Dec 2018 23:36:25 -0500 Subject: [PATCH 05/55] flake8 passes locally --- dash/development/_r_components_generation.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index edaa16bcc6..d8bb127beb 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -202,8 +202,7 @@ def generate_js_metadata_r(project_shortname): format(project_shortname=project_shortname, project_ver=project_ver, rpkgname=rpkgname, - dep_rpp=jsdist[0]['relative_package_path'] - ) + dep_rpp=jsdist[0]['relative_package_path']) function_frame_close = ''') return(deps_metadata) @@ -211,8 +210,7 @@ def generate_js_metadata_r(project_shortname): function_string = ''.join([function_frame_open, function_frame_body, - function_frame_close] - ) + function_frame_close]) return function_string From 049bc4b4b6d1c63ef3a286632cbea09ce0a5ce62 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 11:46:20 -0500 Subject: [PATCH 06/55] addresses format template issue --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index d8bb127beb..c3d5ce1dc4 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -15,7 +15,7 @@ def json_to_r_type(current_prop): if current_prop['defaultValue']['value'].__contains__('\''): argument = current_prop['defaultValue']['value'] else: - argument = "\'{:s}\'".format(current_prop['defaultValue']['value']) + argument = "'{}'".format(current_prop['defaultValue']['value']) elif 'defaultValue' in current_prop and object_type == ['object']: argument = 'list()' elif 'defaultValue' in current_prop and \ From 2c3212efccadca1746420ecca6195924f8b1d27d Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 11:56:15 -0500 Subject: [PATCH 07/55] refactored code to avoid direct use of protocols --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index c3d5ce1dc4..b1721aefaf 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -12,7 +12,7 @@ def json_to_r_type(current_prop): object_type = current_prop['type'].values() if 'defaultValue' in current_prop and object_type == ['string']: - if current_prop['defaultValue']['value'].__contains__('\''): + if "\"" in current_prop['defaultValue']['value']: argument = current_prop['defaultValue']['value'] else: argument = "'{}'".format(current_prop['defaultValue']['value']) From 6f615c1c63aca0ed69d94cb3706e082d772c8a1b Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 11:57:15 -0500 Subject: [PATCH 08/55] removed unnecessary brackets around string in object_type --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index b1721aefaf..8bd20deef7 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -11,7 +11,7 @@ # between R and JSON. def json_to_r_type(current_prop): object_type = current_prop['type'].values() - if 'defaultValue' in current_prop and object_type == ['string']: + if 'defaultValue' in current_prop and object_type == 'string': if "\"" in current_prop['defaultValue']['value']: argument = current_prop['defaultValue']['value'] else: From bd6bf8d78c1cd0b87ceab256c67a5170e1b27446 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 12:14:54 -0500 Subject: [PATCH 09/55] added --r-prefix argument to cli --- dash/development/component_generator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index a044a3e499..0a2d444c3e 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -84,11 +84,6 @@ def generate_components(components_source, project_shortname, generate_imports(project_shortname, components) if generate_r_components: - # -- do all the R stuff here, remove loop as it is unnecessary - # Remove the R NAMESPACE file if it exists, this will be repopulated - # if os.path.isfile('NAMESPACE'): - # os.remove('NAMESPACE') - with open('package.json', 'r') as f: pkg_data = json.load(f) @@ -120,11 +115,17 @@ def cli(): action='store_true', help='Generate Dash components for R, and package for installation.' ) + parser.add_argument( + '--r-prefix', + default='', + help='Inserts a prefix string that will be prepended to DashR component names at generation time.' + ) args = parser.parse_args() generate_components(args.components_source, args.project_shortname, package_info_filename=args.package_info_filename, - generate_r_components=args.rlang) + generate_r_components=args.rlang + ) if __name__ == '__main__': From 413c3da2eaaa91785147c96e0465ea804f25b80b Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 12:18:04 -0500 Subject: [PATCH 10/55] moved import importlib to preamble of .py --- dash/development/component_generator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 0a2d444c3e..c72a0d77b8 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -7,6 +7,7 @@ import os import argparse import shutil +import importlib import functools import pkg_resources @@ -30,8 +31,6 @@ def generate_components(components_source, project_shortname, generate_r_components=False): project_shortname = project_shortname.replace('-', '_').rstrip('/\\') - import importlib - # import component library module importlib.import_module(project_shortname) From a207992ef63bca457d55f6985c19fe17f7108584 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 12:21:56 -0500 Subject: [PATCH 11/55] clarified --rlang help for cli --- dash/development/component_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index c72a0d77b8..66ab284c60 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -112,7 +112,7 @@ def cli(): parser.add_argument( '-r', '--rlang', action='store_true', - help='Generate Dash components for R, and package for installation.' + help='Experimental: write DashR components to R dir, create R package' ) parser.add_argument( '--r-prefix', From ec5733afd9f39635bba00bdf8ca7db1c5e49f9ea Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 12:23:14 -0500 Subject: [PATCH 12/55] edited _r_components_generation.py --- dash/development/_r_components_generation.py | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 8bd20deef7..f81b2d2545 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -28,25 +28,6 @@ def json_to_r_type(current_prop): # pylint: disable=R0914 def generate_class_string_r(name, props, project_shortname, prefix): - """ - Dynamically generate class strings to have nicely formatted documentation, - and function arguments - - Inspired by http://jameso.be/2013/08/06/namedtuple.html - - Parameters - ---------- - name - props - project_shortname - prefix - - Returns - ------- - string - - """ - c = '''{prefix}{name} <- function(..., {default_argtext}) {{ component <- list( @@ -66,8 +47,6 @@ def generate_class_string_r(name, props, project_shortname, prefix): # Here we convert from snake case to camel case package_name = make_package_name_r(project_shortname) - prop_keys = list(props.keys()) - default_paramtext = '' default_argtext = '' default_wildcards = '' From ab3937676741ad42f779b6a1fda2b1401d1a4d86 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:04:25 -0500 Subject: [PATCH 13/55] refactored prefix assignment, added warning msg --- dash/development/component_generator.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 66ab284c60..87c32a6ea5 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -14,7 +14,6 @@ from ._r_components_generation import write_class_file_r from ._r_components_generation import generate_exports_r -from ._r_components_generation import get_shortname_prefix from ._py_components_generation import generate_class_file from ._py_components_generation import generate_imports from ._py_components_generation import generate_classes_files @@ -28,13 +27,26 @@ class _CombinedFormatter(argparse.ArgumentDefaultsHelpFormatter, # pylint: disable=too-many-locals def generate_components(components_source, project_shortname, package_info_filename='package.json', - generate_r_components=False): + generate_r_components=False, + rprefix=None): project_shortname = project_shortname.replace('-', '_').rstrip('/\\') # import component library module importlib.import_module(project_shortname) - prefix = get_shortname_prefix(project_shortname) + if rprefix: + prefix = rprefix + else: + s = [ + x for x in project_shortname.split('_') + if x not in ('dash', 'component', 'components') + ] + prefix = s[-1] + print( + 'Warning: a component prefix was ' + 'not provided. Using {}.'.format(prefix), + file=sys.stderr + ) is_windows = sys.platform == 'win32' @@ -116,14 +128,14 @@ def cli(): ) parser.add_argument( '--r-prefix', - default='', help='Inserts a prefix string that will be prepended to DashR component names at generation time.' ) args = parser.parse_args() generate_components(args.components_source, args.project_shortname, package_info_filename=args.package_info_filename, - generate_r_components=args.rlang + generate_r_components=args.rlang, + rprefix=args.r_prefix ) From 628ec823a2a24d541abc88eab190b6101c2fe972 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:04:51 -0500 Subject: [PATCH 14/55] refactored prefix assignment, removed :s in templates --- dash/development/_r_components_generation.py | 129 +++++++++---------- 1 file changed, 59 insertions(+), 70 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index f81b2d2545..bddde4434f 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -6,6 +6,46 @@ from ._all_keywords import r_keywords from ._py_components_generation import reorder_props +# Declaring longer string templates as globals to improve +# readability, make method logic clearer to anyone inspecting +# code below + +r_component_string = '''{prefix}{name} <- function(..., {default_argtext}) {{ + + component <- list( + props = list({default_paramtext}), + type = '{name}', + namespace = '{project_shortname}', + propNames = c({prop_names}), + package = '{package_name}' + ) + + component$props <- filter_null(component$props) + component <- append_wildcard_props(component, wildcards = {default_wildcards}, ...) + + structure(component, class = c('dash_component', 'list')) +}}''' # noqa:E501 + +# the following string represents all the elements in an object +# of the html_dependency class, which will be propagated by +# iterating over _js_dist in __init__.py +function_frame_element = '''`{dep_name}` = structure(list(name = "{dep_name}", +version = "{project_ver}", src = list(href = NULL, +file = "lib/"), meta = NULL, +script = "{dep_rpp}", +stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", +all_files = FALSE), class = "html_dependency")''' + +function_frame_body = '''`{project_shortname}` = structure(list(name = "{project_shortname}", +version = "{project_ver}", src = list(href = NULL, +file = "lib/"), meta = NULL, +script = "{dep_rpp}", +stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", +all_files = FALSE), class = "html_dependency")''' + +function_frame_close = ''') +return(deps_metadata) +}''' # This is an initial attempt at resolving type inconsistencies # between R and JSON. @@ -28,32 +68,18 @@ def json_to_r_type(current_prop): # pylint: disable=R0914 def generate_class_string_r(name, props, project_shortname, prefix): - c = '''{prefix}{name} <- function(..., {default_argtext}) {{ - - component <- list( - props = list({default_paramtext}), - type = '{name}', - namespace = '{project_shortname}', - propNames = c({prop_names}), - package = '{package_name}' - ) - - component$props <- filter_null(component$props) - component <- append_wildcard_props(component, wildcards = {default_wildcards}, ...) - - structure(component, class = c('dash_component', 'list')) -}}''' # noqa:E501 - # Here we convert from snake case to camel case package_name = make_package_name_r(project_shortname) + prop_keys = props.keys() + default_paramtext = '' default_argtext = '' default_wildcards = '' # Produce a string with all property names other than WCs prop_names = ", ".join( - ('\'{:s}\''.format(p)) + ("{}".format(p)) for p in prop_keys if '*' not in p and p not in ['setProps', 'dashEvents', 'fireEvent'] @@ -62,7 +88,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): # in R, we set parameters with no defaults to NULL # Here we'll do that if no default value exists default_wildcards += ", ".join( - ('\'{:s}\''.format(p)) + ('\'{}\''.format(p)) for p in prop_keys if '*' in p ) @@ -70,12 +96,12 @@ def generate_class_string_r(name, props, project_shortname, prefix): if default_wildcards == '': default_wildcards = 'NULL' else: - default_wildcards = 'c({:s})'.format(default_wildcards) + default_wildcards = 'c({})'.format(default_wildcards) default_argtext += ", ".join( - ('{:s}={}'.format(p, json_to_r_type(props[p])) + ('{}={}'.format(p, json_to_r_type(props[p])) if 'defaultValue' in props[p] else - '{:s}=NULL'.format(p)) + '{}=NULL'.format(p)) for p in prop_keys if not p.endswith("-*") and p not in r_keywords and @@ -87,16 +113,16 @@ def generate_class_string_r(name, props, project_shortname, prefix): # pylint: disable=C0301 default_paramtext += ", ".join( - ('{:s}={:s}'.format(p, p) + ('{}={}'.format(p, p) if p != "children" else - '{:s}=c(children, assert_valid_children(..., wildcards = {:s}))' + '{}=c(children, assert_valid_children(..., wildcards = {}))' .format(p, default_wildcards)) for p in props.keys() if not p.endswith("-*") and p not in r_keywords and p not in ['setProps', 'dashEvents', 'fireEvent'] ) - return c.format(prefix=prefix, + return r_component_string.format(prefix=prefix, name=name, default_argtext=default_argtext, default_paramtext=default_paramtext, @@ -143,23 +169,13 @@ def generate_js_metadata_r(project_shortname): function_frame = [] - # the following string represents all the elements in an object - # of the html_dependency class, which will be propagated by - # iterating over _js_dist in __init__.py - function_frame_element = '''`{dep_name}` = structure(list(name = "{dep_name}", -version = "{project_ver}", src = list(href = NULL, -file = "lib/"), meta = NULL, -script = "{dep_rpp}", -stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", -all_files = FALSE), class = "html_dependency")''' - # pylint: disable=consider-using-enumerate if len(jsdist) > 1: for dep in range(len(jsdist)): if jsdist[dep]['relative_package_path'].__contains__('dash_'): dep_name = jsdist[dep]['relative_package_path'].split('.')[0] else: - dep_name = '{:s}_{:s}'.format(project_shortname, str(dep)) + dep_name = '{}_{}'.format(project_shortname, str(dep)) project_ver = str(dep) function_frame += [function_frame_element.format( dep_name=dep_name, @@ -172,21 +188,12 @@ def generate_js_metadata_r(project_shortname): function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: # pylint: disable=line-too-long - function_frame_body = '''`{project_shortname}` = structure(list(name = "{project_shortname}", -version = "{project_ver}", src = list(href = NULL, -file = "lib/"), meta = NULL, -script = "{dep_rpp}", -stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", -all_files = FALSE), class = "html_dependency")'''.\ + function_frame_body = function_frame_body.\ format(project_shortname=project_shortname, project_ver=project_ver, rpkgname=rpkgname, dep_rpp=jsdist[0]['relative_package_path']) - function_frame_close = ''') -return(deps_metadata) -}''' - function_string = ''.join([function_frame_open, function_frame_body, function_frame_close]) @@ -210,7 +217,7 @@ def write_help_file_r(name, props, description, prefix): """ - file_name = '{:s}{:s}.Rd'.format(prefix, name) + file_name = '{}{}.Rd'.format(prefix, name) prop_keys = list(props.keys()) default_argtext = '' @@ -220,9 +227,9 @@ def write_help_file_r(name, props, description, prefix): props = reorder_props(props=props) default_argtext += ", ".join( - '{:s}={}'.format(p, json_to_r_type(props[p])) + '{}={}'.format(p, json_to_r_type(props[p])) if 'defaultValue' in props[p] else - '{:s}=NULL'.format(p) + '{}=NULL'.format(p) for p in prop_keys if not p.endswith("-*") and p not in r_keywords and @@ -230,7 +237,7 @@ def write_help_file_r(name, props, description, prefix): ) item_text += "\n\n".join( - ('\\item{{{:s}}}{{{:s}}}'.format(p, props[p]['description'])) + ('\\item{{{}}}{{{}}}'.format(p, props[p]['description'])) for p in prop_keys if not p.endswith("-*") and p not in r_keywords and @@ -294,7 +301,7 @@ def write_class_file_r(name, project_shortname, prefix ) - file_name = "{:s}{:s}.R".format(prefix, name) + file_name = "{}{}.R".format(prefix, name) if not os.path.exists('R'): os.makedirs('R') @@ -334,7 +341,7 @@ def generate_export_string_r(name, prefix): if not name.endswith('-*') and \ str(name) not in r_keywords and \ str(name) not in ['setProps', 'children', 'dashEvents']: - return 'export({:s}{:s})\n'.format(prefix, name) + return 'export({}{})\n'.format(prefix, name) def write_js_metadata_r(project_shortname): @@ -497,24 +504,6 @@ def make_package_name_r(namestring): return first + ''.join(word.capitalize() for word in rest) -# For the R packages corresponding to the component -# libraries, use 'project_shortname' to determine -# the prefix for the set of R functions we're -# generating that correspond to Dash components. -# -# e.g. the graph component in DCC will be prepended -# with 'core', so that the R function is coreGraph() -def get_shortname_prefix(project_shortname): - if project_shortname == 'dash_html_components': - prefix = 'html' - elif project_shortname == 'dash_core_components': - prefix = 'core' - else: - prefix = '' - - return prefix - - # pylint: disable=unused-argument def generate_exports_r(project_shortname, components, @@ -527,7 +516,7 @@ def generate_exports_r(project_shortname, if not component.endswith('-*') and \ str(component) not in r_keywords and \ str(component) not in ['setProps', 'children', 'dashEvents']: - export_string += 'export({:s}{:s})\n'.format(prefix, component) + export_string += 'export({}{})\n'.format(prefix, component) # now, bundle up the package information and create all the requisite # elements of an R package, so that the end result is installable either From 6afe2185efa981dc2b22d0a4060c3023d602e8c8 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:13:58 -0500 Subject: [PATCH 15/55] updated docstrings --- dash/development/_r_components_generation.py | 32 +++++--------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index bddde4434f..79203ecb88 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -26,9 +26,12 @@ structure(component, class = c('dash_component', 'list')) }}''' # noqa:E501 -# the following string represents all the elements in an object +# the following strings represent all the elements in an object # of the html_dependency class, which will be propagated by # iterating over _js_dist in __init__.py +function_frame_open = '''.{rpkgname}_js_metadata <- function() {{ +deps_metadata <- list(''' + function_frame_element = '''`{dep_name}` = structure(list(name = "{dep_name}", version = "{project_ver}", src = list(href = NULL, file = "lib/"), meta = NULL, @@ -163,9 +166,7 @@ def generate_js_metadata_r(project_shortname): # here we define an opening, element, and closing string -- # if the total number of dependencies > 1, we can concatenate # them and write a list object in R with multiple elements - function_frame_open = '''.{rpkgname}_js_metadata <- function() {{ -deps_metadata <- list( -'''.format(rpkgname=rpkgname) + function_frame_open = function_frame_open.format(rpkgname=rpkgname) function_frame = [] @@ -187,7 +188,6 @@ def generate_js_metadata_r(project_shortname): ] function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: - # pylint: disable=line-too-long function_frame_body = function_frame_body.\ format(project_shortname=project_shortname, project_ver=project_ver, @@ -277,22 +277,6 @@ def write_class_file_r(name, description, project_shortname, prefix=None): - """ - Generate a R class file (.R) given a class string - - Parameters - ---------- - name - props - description - project_shortname - prefix - - Returns - ------- - - """ - import_string =\ "# AUTO GENERATED FILE - DO NOT EDIT\n\n" class_string = generate_class_string_r( @@ -346,13 +330,13 @@ def generate_export_string_r(name, prefix): def write_js_metadata_r(project_shortname): """ - Write an internal (not exported) function to return all JS - dependencies as required by htmlDependency package given a + Write an internal (not exported) R function to return all JS + dependencies as required by htmltools package given a function string Parameters ---------- - project_shortname + project_shortname = hyphenated string, e.g. dash-html-components Returns ------- From d321153d475ad960b28087b33c73a5fa3a94db6f Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:21:35 -0500 Subject: [PATCH 16/55] rm unnecessary parens --- dash/development/_r_components_generation.py | 39 ++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 79203ecb88..c4e55cd4d3 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -50,6 +50,7 @@ return(deps_metadata) }''' + # This is an initial attempt at resolving type inconsistencies # between R and JSON. def json_to_r_type(current_prop): @@ -82,7 +83,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): # Produce a string with all property names other than WCs prop_names = ", ".join( - ("{}".format(p)) + "{}".format(p) for p in prop_keys if '*' not in p and p not in ['setProps', 'dashEvents', 'fireEvent'] @@ -102,9 +103,9 @@ def generate_class_string_r(name, props, project_shortname, prefix): default_wildcards = 'c({})'.format(default_wildcards) default_argtext += ", ".join( - ('{}={}'.format(p, json_to_r_type(props[p])) - if 'defaultValue' in props[p] else - '{}=NULL'.format(p)) + '{}={}'.format(p, json_to_r_type(props[p])) + if 'defaultValue' in props[p] else + '{}=NULL'.format(p) for p in prop_keys if not p.endswith("-*") and p not in r_keywords and @@ -116,23 +117,23 @@ def generate_class_string_r(name, props, project_shortname, prefix): # pylint: disable=C0301 default_paramtext += ", ".join( - ('{}={}'.format(p, p) + '{}={}'.format(p, p) if p != "children" else - '{}=c(children, assert_valid_children(..., wildcards = {}))' - .format(p, default_wildcards)) + '{}=c(children, assert_valid_children(..., wildcards = {}))' + .format(p, default_wildcards) for p in props.keys() if not p.endswith("-*") and p not in r_keywords and p not in ['setProps', 'dashEvents', 'fireEvent'] ) return r_component_string.format(prefix=prefix, - name=name, - default_argtext=default_argtext, - default_paramtext=default_paramtext, - project_shortname=project_shortname, - prop_names=prop_names, - package_name=package_name, - default_wildcards=default_wildcards) + name=name, + default_argtext=default_argtext, + default_paramtext=default_paramtext, + project_shortname=project_shortname, + prop_names=prop_names, + package_name=package_name, + default_wildcards=default_wildcards) # pylint: disable=R0914 @@ -185,10 +186,10 @@ def generate_js_metadata_r(project_shortname): project_shortname=project_shortname, dep_rpp=jsdist[dep]['relative_package_path'] ) - ] + ] function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: - function_frame_body = function_frame_body.\ + function_frame_body = function_frame_body. \ format(project_shortname=project_shortname, project_ver=project_ver, rpkgname=rpkgname, @@ -237,7 +238,7 @@ def write_help_file_r(name, props, description, prefix): ) item_text += "\n\n".join( - ('\\item{{{}}}{{{}}}'.format(p, props[p]['description'])) + '\\item{{{}}}{{{}}}'.format(p, props[p]['description']) for p in prop_keys if not p.endswith("-*") and p not in r_keywords and @@ -277,7 +278,7 @@ def write_class_file_r(name, description, project_shortname, prefix=None): - import_string =\ + import_string = \ "# AUTO GENERATED FILE - DO NOT EDIT\n\n" class_string = generate_class_string_r( name, @@ -409,7 +410,7 @@ def generate_rpkg(pkg_data, if not os.path.isfile('LICENSE'): os.symlink("LICENSE.txt", "LICENSE") - import_string =\ + import_string = \ '# AUTO GENERATED FILE - DO NOT EDIT\n\n' description_string = '''Package: {package_name} From 759941e859ac42a1655ceaa9e98f01d255386ed4 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:33:24 -0500 Subject: [PATCH 17/55] eliminate unnecessary rewrites; fix prop_keys assignment --- dash/development/_r_components_generation.py | 52 +++++++++----------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index c4e55cd4d3..02e65fa62d 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -9,7 +9,6 @@ # Declaring longer string templates as globals to improve # readability, make method logic clearer to anyone inspecting # code below - r_component_string = '''{prefix}{name} <- function(..., {default_argtext}) {{ component <- list( @@ -50,6 +49,21 @@ return(deps_metadata) }''' +help_string = '''% Auto-generated: do not edit by hand +\\name{{{prefix}{name}}} +\\alias{{{prefix}{name}}} +\\title{{{name} component}} +\\description{{ +{description} +}} +\\usage{{ +{prefix}{name}(..., {default_argtext}) +}} +\\arguments{{ +{item_text} +}} +''' + # This is an initial attempt at resolving type inconsistencies # between R and JSON. @@ -219,7 +233,7 @@ def write_help_file_r(name, props, description, prefix): """ file_name = '{}{}.Rd'.format(prefix, name) - prop_keys = list(props.keys()) + prop_keys = props.keys() default_argtext = '' item_text = '' @@ -245,23 +259,6 @@ def write_help_file_r(name, props, description, prefix): p not in ['setProps', 'dashEvents', 'fireEvent'] ) - help_string = '''% Auto-generated: do not edit by hand -\\name{{{prefix}{name}}} -\\alias{{{prefix}{name}}} -\\title{{{name} component}} -\\description{{ -{description} -}} -\\usage{{ -{prefix}{name}(..., {default_argtext}) -}} -\\arguments{{ -{item_text} -}} - ''' - if not os.path.exists('man'): - os.makedirs('man') - file_path = os.path.join('man', file_name) with open(file_path, 'w') as f: f.write(help_string.format( @@ -296,15 +293,6 @@ def write_class_file_r(name, f.write(import_string) f.write(class_string) - # generate the internal (not exported to the user) functions which - # supply the JavaScript dependencies to the htmlDependency package, - # which is required by DashR (this avoids having to generate an - # RData file from within Python, given the current package generation - # workflow) - write_js_metadata_r( - project_shortname - ) - # generate the R help pages for each of the Dash components that we # are transpiling -- this is done to avoid using Roxygen2 syntax, # we may eventually be able to generate similar documentation using @@ -469,6 +457,14 @@ def generate_rpkg(pkg_data, ^.*\.Rproj$ ^\.Rproj\.user$ ''' + # generate the internal (not exported to the user) functions which + # supply the JavaScript dependencies to the htmlDependency package, + # which is required by DashR (this avoids having to generate an + # RData file from within Python, given the current package generation + # workflow) + write_js_metadata_r( + project_shortname + ) with open('NAMESPACE', 'w') as f: f.write(import_string) From 600ef853eecbfb33e3f45607f5b56ac34493473d Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:35:22 -0500 Subject: [PATCH 18/55] relocated check for R dir to component_generator.py --- dash/development/_r_components_generation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 02e65fa62d..f152e42db2 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -285,9 +285,6 @@ def write_class_file_r(name, ) file_name = "{}{}.R".format(prefix, name) - if not os.path.exists('R'): - os.makedirs('R') - file_path = os.path.join('R', file_name) with open(file_path, 'w') as f: f.write(import_string) From d13304319a613b178c448d7b9645e29241dce291 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:46:12 -0500 Subject: [PATCH 19/55] added line to pull in CSS deps to inst/lib --- dash/development/_r_components_generation.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index f152e42db2..47ce9256d0 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -6,6 +6,9 @@ from ._all_keywords import r_keywords from ._py_components_generation import reorder_props +import shutil +import glob + # Declaring longer string templates as globals to improve # readability, make method logic clearer to anyone inspecting # code below @@ -343,9 +346,6 @@ def write_js_metadata_r(project_shortname): f.write(function_string) # now copy over all JS dependencies from the (Python) components dir - import shutil - import glob - # the inst/lib directory for the package won't exist on first call # create this directory if it is missing if not os.path.exists('inst'): @@ -357,6 +357,9 @@ def write_js_metadata_r(project_shortname): for javascript in glob.glob('{}/*.js'.format(project_shortname)): shutil.copy(javascript, 'inst/lib/') + for css in glob.glob('{}/*.css'.format(project_shortname)): + shutil.copy(css, 'inst/lib/') + # pylint: disable=R0914 def generate_rpkg(pkg_data, From a3804eb3bc85ec938e0ed8db78c438459086220e Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 14:49:40 -0500 Subject: [PATCH 20/55] added check for R dir to component_generator.py --- dash/development/component_generator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 87c32a6ea5..dd8af28474 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -80,6 +80,10 @@ def generate_components(components_source, project_shortname, generator_methods = [generate_class_file] if generate_r_components: + if not os.path.exists('man'): + os.makedirs('man') + if not os.path.exists('R'): + os.makedirs('R') generator_methods.append( functools.partial(write_class_file_r, prefix=prefix)) From 3f2eb22f6f6dd8dce8efd889c42810c680b76290 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:05:10 -0500 Subject: [PATCH 21/55] renamed/refactored make_package_name_r to snake_case_to_camel_case --- dash/development/_r_components_generation.py | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 47ce9256d0..8427051f5f 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -90,7 +90,7 @@ def json_to_r_type(current_prop): # pylint: disable=R0914 def generate_class_string_r(name, props, project_shortname, prefix): # Here we convert from snake case to camel case - package_name = make_package_name_r(project_shortname) + package_name = snake_case_to_camel_case(project_shortname) prop_keys = props.keys() @@ -177,7 +177,7 @@ def generate_js_metadata_r(project_shortname): jsdist = getattr(mod, '_js_dist', []) project_ver = getattr(mod, '__version__', []) - rpkgname = make_package_name_r(project_shortname) + rpkgname = snake_case_to_camel_case(project_shortname) # since _js_dist may suggest more than one dependency, need # a way to iterate over all dependencies for a given set. @@ -379,14 +379,16 @@ def generate_rpkg(pkg_data, """ # Leverage package.json to import specifics which are also applicable - # to R package that we're generating here - package_name = make_package_name_r(project_shortname) - package_description = pkg_data['description'] - package_version = pkg_data['version'] - package_issues = pkg_data['bugs']['url'] - package_url = pkg_data['homepage'] + # to R package that we're generating here, use .get in case the key + # does not exist in package.json - package_author = pkg_data['author'] + package_name = snake_case_to_camel_case(project_shortname) + package_description = pkg_data.get('description', '') + package_version = pkg_data.get('version', '0.01') + package_issues = pkg_data['bugs'].get('url', '') + package_url = pkg_data.get('homepage', '') + + package_author = pkg_data.get('author') package_author_no_email = package_author.split(" <")[0] + ' [aut]' @@ -480,9 +482,9 @@ def generate_rpkg(pkg_data, # This converts a string from snake case to camel case # Not required for R package name to be in camel case, # but probably more conventional this way -def make_package_name_r(namestring): - first, rest = namestring.split('_')[0], namestring.split('_')[1:] - return first + ''.join(word.capitalize() for word in rest) +def snake_case_to_camel_case(namestring): + s = namestring.split('_') + return s[0] + ''.join(w.capitalize() for w in s[1:]) # pylint: disable=unused-argument From 6f954e5172011e8cfb0baf8d9728cb2a47fec83f Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:09:50 -0500 Subject: [PATCH 22/55] rm more parens, replace apostrophes with dblquotes --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 8427051f5f..4504138017 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -109,7 +109,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): # in R, we set parameters with no defaults to NULL # Here we'll do that if no default value exists default_wildcards += ", ".join( - ('\'{}\''.format(p)) + "{}".format(p) for p in prop_keys if '*' in p ) From bb352e8feefaac155b44071ebea6894be23e2da5 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:17:55 -0500 Subject: [PATCH 23/55] remove spaces --- dash/development/_r_components_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 4504138017..4b3cf0c159 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -278,7 +278,7 @@ def write_class_file_r(name, description, project_shortname, prefix=None): - import_string = \ + import_string =\ "# AUTO GENERATED FILE - DO NOT EDIT\n\n" class_string = generate_class_string_r( name, @@ -400,7 +400,7 @@ def generate_rpkg(pkg_data, if not os.path.isfile('LICENSE'): os.symlink("LICENSE.txt", "LICENSE") - import_string = \ + import_string =\ '# AUTO GENERATED FILE - DO NOT EDIT\n\n' description_string = '''Package: {package_name} From ffe7f5f96f064e173ff20de457be992ae188bf8f Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:21:47 -0500 Subject: [PATCH 24/55] refactor direct access to JSON keys to use .get instead --- dash/development/_r_components_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 4b3cf0c159..c5d43eebd2 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -393,9 +393,9 @@ def generate_rpkg(pkg_data, package_author_no_email = package_author.split(" <")[0] + ' [aut]' if not (os.path.isfile('LICENSE') or os.path.isfile('LICENSE.txt')): - package_license = pkg_data['license'] + package_license = pkg_data.get('license', '') else: - package_license = pkg_data['license'] + ' + file LICENSE' + package_license = pkg_data.get('license', '') + ' + file LICENSE' # R requires that the LICENSE.txt file be named LICENSE if not os.path.isfile('LICENSE'): os.symlink("LICENSE.txt", "LICENSE") From 3cf3b22d146a04f2bd50c8c01d1a6655af63e2a7 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:27:01 -0500 Subject: [PATCH 25/55] fixed 2 pylint issues --- dash/development/component_generator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index dd8af28474..4688f9e22c 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -132,7 +132,8 @@ def cli(): ) parser.add_argument( '--r-prefix', - help='Inserts a prefix string that will be prepended to DashR component names at generation time.' + help='Inserts a prefix string that will be prepended to DashR component' + 'names at generation time.' ) args = parser.parse_args() @@ -140,7 +141,7 @@ def cli(): package_info_filename=args.package_info_filename, generate_r_components=args.rlang, rprefix=args.r_prefix - ) + ) if __name__ == '__main__': From 99ba5c7aaf13d9cc8e7f4f3704a4eea6b1f2aafa Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:33:15 -0500 Subject: [PATCH 26/55] fixed import order in _r_components_generation.py --- dash/development/_r_components_generation.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index c5d43eebd2..a3bd1a78ab 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -1,3 +1,6 @@ +import shutil +import glob + from __future__ import absolute_import import os @@ -6,8 +9,6 @@ from ._all_keywords import r_keywords from ._py_components_generation import reorder_props -import shutil -import glob # Declaring longer string templates as globals to improve # readability, make method logic clearer to anyone inspecting @@ -135,9 +136,9 @@ def generate_class_string_r(name, props, project_shortname, prefix): # pylint: disable=C0301 default_paramtext += ", ".join( '{}={}'.format(p, p) - if p != "children" else + if p != "children" else '{}=c(children, assert_valid_children(..., wildcards = {}))' - .format(p, default_wildcards) + .format(p, default_wildcards) for p in props.keys() if not p.endswith("-*") and p not in r_keywords and @@ -203,7 +204,7 @@ def generate_js_metadata_r(project_shortname): project_shortname=project_shortname, dep_rpp=jsdist[dep]['relative_package_path'] ) - ] + ] function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: function_frame_body = function_frame_body. \ From 7614c4d499a4db7be4f196b609be4ce585cc294e Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:50:58 -0500 Subject: [PATCH 27/55] edits to satisfy pylint --- dash/development/_r_components_generation.py | 21 ++++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index a3bd1a78ab..2257ef0f21 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -1,10 +1,9 @@ -import shutil -import glob - from __future__ import absolute_import import os import sys +import shutil +import glob from ._all_keywords import r_keywords from ._py_components_generation import reorder_props @@ -32,24 +31,24 @@ # the following strings represent all the elements in an object # of the html_dependency class, which will be propagated by # iterating over _js_dist in __init__.py -function_frame_open = '''.{rpkgname}_js_metadata <- function() {{ +frame_open_template = '''.{rpkgname}_js_metadata <- function() {{ deps_metadata <- list(''' -function_frame_element = '''`{dep_name}` = structure(list(name = "{dep_name}", +frame_element_template = '''`{dep_name}` = structure(list(name = "{dep_name}", version = "{project_ver}", src = list(href = NULL, file = "lib/"), meta = NULL, script = "{dep_rpp}", stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", all_files = FALSE), class = "html_dependency")''' -function_frame_body = '''`{project_shortname}` = structure(list(name = "{project_shortname}", +frame_body_template = '''`{project_shortname}` = structure(list(name = "{project_shortname}", version = "{project_ver}", src = list(href = NULL, file = "lib/"), meta = NULL, script = "{dep_rpp}", stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", all_files = FALSE), class = "html_dependency")''' -function_frame_close = ''') +frame_close_template = ''') return(deps_metadata) }''' @@ -185,7 +184,7 @@ def generate_js_metadata_r(project_shortname): # here we define an opening, element, and closing string -- # if the total number of dependencies > 1, we can concatenate # them and write a list object in R with multiple elements - function_frame_open = function_frame_open.format(rpkgname=rpkgname) + function_frame_open = frame_open_template.format(rpkgname=rpkgname) function_frame = [] @@ -197,7 +196,7 @@ def generate_js_metadata_r(project_shortname): else: dep_name = '{}_{}'.format(project_shortname, str(dep)) project_ver = str(dep) - function_frame += [function_frame_element.format( + function_frame += [frame_element_template.format( dep_name=dep_name, project_ver=project_ver, rpkgname=rpkgname, @@ -207,7 +206,7 @@ def generate_js_metadata_r(project_shortname): ] function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: - function_frame_body = function_frame_body. \ + function_frame_body = frame_body_template. \ format(project_shortname=project_shortname, project_ver=project_ver, rpkgname=rpkgname, @@ -215,7 +214,7 @@ def generate_js_metadata_r(project_shortname): function_string = ''.join([function_frame_open, function_frame_body, - function_frame_close]) + frame_close_template]) return function_string From 8a0891496460f1184cddd73f36ea791e4fd59e6c Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 15:54:00 -0500 Subject: [PATCH 28/55] edits to resolve pylint/flake8 complaints --- dash/development/component_generator.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 4688f9e22c..3a96a237be 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -132,16 +132,15 @@ def cli(): ) parser.add_argument( '--r-prefix', - help='Inserts a prefix string that will be prepended to DashR component' - 'names at generation time.' + help='Inserts a prefix string that will be prepended to DashR' + 'component names at generation time.' ) args = parser.parse_args() generate_components(args.components_source, args.project_shortname, package_info_filename=args.package_info_filename, generate_r_components=args.rlang, - rprefix=args.r_prefix - ) + rprefix=args.r_prefix) if __name__ == '__main__': From 8d71c0c5fad7fa61f377cbfee49452394c1b8fc2 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 11 Dec 2018 16:29:18 -0500 Subject: [PATCH 29/55] added backticks --- dash/development/component_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 3a96a237be..3ff6c4b9dd 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -44,7 +44,7 @@ def generate_components(components_source, project_shortname, prefix = s[-1] print( 'Warning: a component prefix was ' - 'not provided. Using {}.'.format(prefix), + 'not provided. Using `{}`.'.format(prefix), file=sys.stderr ) From 75d29b702bc44af9b3d688d86904e3154563471b Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 11:16:01 -0500 Subject: [PATCH 30/55] fixed code regression caused by modifying key template in default_wildcards --- dash/development/_r_components_generation.py | 24 +++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 2257ef0f21..8729653e65 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -1,4 +1,5 @@ from __future__ import absolute_import +from __future__ import print_function import os import sys @@ -109,7 +110,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): # in R, we set parameters with no defaults to NULL # Here we'll do that if no default value exists default_wildcards += ", ".join( - "{}".format(p) + '\'{}\''.format(p) for p in prop_keys if '*' in p ) @@ -385,8 +386,25 @@ def generate_rpkg(pkg_data, package_name = snake_case_to_camel_case(project_shortname) package_description = pkg_data.get('description', '') package_version = pkg_data.get('version', '0.01') - package_issues = pkg_data['bugs'].get('url', '') - package_url = pkg_data.get('homepage', '') + + if 'bugs' in pkg_data.keys(): + package_issues = pkg_data['bugs'].get('url', '') + else: + package_issues = '' + print( + 'Warning: a URL for bug reports was ' + 'not provided. Empty string inserted.', + file=sys.stderr + ) + + if 'homepage' in pkg_data.keys(): + package_url = pkg_data.get('homepage', '') + else: + package_url = '' + print( + 'Warning: a homepage URL was not provided. Empty string inserted.', + file=sys.stderr + ) package_author = pkg_data.get('author') From fcbe97124d5d683c46ca88d98d17c5a5ac80466b Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 11:30:56 -0500 Subject: [PATCH 31/55] at suggestion of rmarren1, strip _r suffix from proposed methods --- dash/development/_r_components_generation.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 8729653e65..cafe981ba7 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -89,7 +89,7 @@ def json_to_r_type(current_prop): # pylint: disable=R0914 -def generate_class_string_r(name, props, project_shortname, prefix): +def generate_class_string(name, props, project_shortname, prefix): # Here we convert from snake case to camel case package_name = snake_case_to_camel_case(project_shortname) @@ -155,7 +155,7 @@ def generate_class_string_r(name, props, project_shortname, prefix): # pylint: disable=R0914 -def generate_js_metadata_r(project_shortname): +def generate_js_metadata(project_shortname): """ Dynamically generate R function to supply JavaScript dependency information required by htmltools package, @@ -220,7 +220,7 @@ def generate_js_metadata_r(project_shortname): return function_string -def write_help_file_r(name, props, description, prefix): +def write_help_file(name, props, description, prefix): """ Write R documentation file (.Rd) given component name and properties @@ -274,14 +274,14 @@ def write_help_file_r(name, props, description, prefix): )) -def write_class_file_r(name, +def write_class_file(name, props, description, project_shortname, prefix=None): import_string =\ "# AUTO GENERATED FILE - DO NOT EDIT\n\n" - class_string = generate_class_string_r( + class_string = generate_class_string( name, props, project_shortname, @@ -300,7 +300,7 @@ def write_class_file_r(name, # doxygen and an R plugin, but for now we'll just do it on our own # from within Python # noqa E344 - write_help_file_r( + write_help_file( name, props, description, @@ -311,14 +311,14 @@ def write_class_file_r(name, # pylint: disable=inconsistent-return-statements -def generate_export_string_r(name, prefix): +def generate_export_string(name, prefix): if not name.endswith('-*') and \ str(name) not in r_keywords and \ str(name) not in ['setProps', 'children', 'dashEvents']: return 'export({}{})\n'.format(prefix, name) -def write_js_metadata_r(project_shortname): +def write_js_metadata(project_shortname): """ Write an internal (not exported) R function to return all JS dependencies as required by htmltools package given a @@ -332,7 +332,7 @@ def write_js_metadata_r(project_shortname): ------- """ - function_string = generate_js_metadata_r( + function_string = generate_js_metadata( project_shortname ) file_name = "internal.R" @@ -482,7 +482,7 @@ def generate_rpkg(pkg_data, # which is required by DashR (this avoids having to generate an # RData file from within Python, given the current package generation # workflow) - write_js_metadata_r( + write_js_metadata( project_shortname ) @@ -506,7 +506,7 @@ def snake_case_to_camel_case(namestring): # pylint: disable=unused-argument -def generate_exports_r(project_shortname, +def generate_exports(project_shortname, components, metadata, pkg_data, From 5c95c87be8a8ef4c9f620c21daf138c77e9ada8d Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 11:31:07 -0500 Subject: [PATCH 32/55] at suggestion of rmarren1, strip _r suffix from proposed methods --- dash/development/component_generator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 3ff6c4b9dd..0b6112ead8 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -12,8 +12,8 @@ import pkg_resources -from ._r_components_generation import write_class_file_r -from ._r_components_generation import generate_exports_r +from ._r_components_generation import write_class_file +from ._r_components_generation import generate_exports from ._py_components_generation import generate_class_file from ._py_components_generation import generate_imports from ._py_components_generation import generate_classes_files @@ -85,7 +85,7 @@ def generate_components(components_source, project_shortname, if not os.path.exists('R'): os.makedirs('R') generator_methods.append( - functools.partial(write_class_file_r, prefix=prefix)) + functools.partial(write_class_file, prefix=prefix)) components = generate_classes_files( project_shortname, @@ -102,7 +102,7 @@ def generate_components(components_source, project_shortname, with open('package.json', 'r') as f: pkg_data = json.load(f) - generate_exports_r( + generate_exports( project_shortname, components, metadata, pkg_data, prefix ) From fa8fb3888f4423f8ce0f933b45b3760547263d28 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 11:34:58 -0500 Subject: [PATCH 33/55] edits to address pylint issues in _r_components_generation.py --- dash/development/_r_components_generation.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index cafe981ba7..e690185c0a 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -275,10 +275,10 @@ def write_help_file(name, props, description, prefix): def write_class_file(name, - props, - description, - project_shortname, - prefix=None): + props, + description, + project_shortname, + prefix=None): import_string =\ "# AUTO GENERATED FILE - DO NOT EDIT\n\n" class_string = generate_class_string( @@ -507,11 +507,11 @@ def snake_case_to_camel_case(namestring): # pylint: disable=unused-argument def generate_exports(project_shortname, - components, - metadata, - pkg_data, - prefix, - **kwargs): + components, + metadata, + pkg_data, + prefix, + **kwargs): export_string = '' for component in components: if not component.endswith('-*') and \ From a6bfc1ec76f560a7003d0659e894818cfeb52d3a Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 11:57:01 -0500 Subject: [PATCH 34/55] s/props.keys()/prop_keys --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index e690185c0a..d5e0ebdab6 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -139,7 +139,7 @@ def generate_class_string(name, props, project_shortname, prefix): if p != "children" else '{}=c(children, assert_valid_children(..., wildcards = {}))' .format(p, default_wildcards) - for p in props.keys() + for p in prop_keys if not p.endswith("-*") and p not in r_keywords and p not in ['setProps', 'dashEvents', 'fireEvent'] From 2094f36dc5b1fe18ad2cffc2fe56bf619726b872 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 11:58:49 -0500 Subject: [PATCH 35/55] address suggestion by rmarren1 to correct formatting of bracket --- dash/development/_r_components_generation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index d5e0ebdab6..32577d057e 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -203,8 +203,7 @@ def generate_js_metadata(project_shortname): rpkgname=rpkgname, project_shortname=project_shortname, dep_rpp=jsdist[dep]['relative_package_path'] - ) - ] + )] function_frame_body = ',\n'.join(function_frame) elif len(jsdist) == 1: function_frame_body = frame_body_template. \ From 05b8a7eb33a3ecde249cde1f57162ec75d3ce09f Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 13:52:43 -0500 Subject: [PATCH 36/55] refactored to avoid :camel: in prop_keys filtering --- dash/development/_r_components_generation.py | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 32577d057e..060090100a 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -120,14 +120,18 @@ def generate_class_string(name, props, project_shortname, prefix): else: default_wildcards = 'c({})'.format(default_wildcards) + # Filter props to remove those we don't want to expose + for p in prop_keys: + if p.endswith("-*") \ + or p in r_keywords \ + or p in ['setProps', 'dashEvents', 'fireEvent']: + prop_keys.remove(p) + default_argtext += ", ".join( '{}={}'.format(p, json_to_r_type(props[p])) if 'defaultValue' in props[p] else '{}=NULL'.format(p) for p in prop_keys - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] ) if 'children' in props: @@ -140,9 +144,6 @@ def generate_class_string(name, props, project_shortname, prefix): '{}=c(children, assert_valid_children(..., wildcards = {}))' .format(p, default_wildcards) for p in prop_keys - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] ) return r_component_string.format(prefix=prefix, name=name, @@ -244,22 +245,23 @@ def write_help_file(name, props, description, prefix): # Ensure props are ordered with children first props = reorder_props(props=props) + # Filter props to remove those we don't want to expose + for p in prop_keys: + if p.endswith("-*") \ + or p in r_keywords \ + or p in ['setProps', 'dashEvents', 'fireEvent']: + prop_keys.remove(p) + default_argtext += ", ".join( '{}={}'.format(p, json_to_r_type(props[p])) if 'defaultValue' in props[p] else '{}=NULL'.format(p) for p in prop_keys - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] ) item_text += "\n\n".join( '\\item{{{}}}{{{}}}'.format(p, props[p]['description']) for p in prop_keys - if not p.endswith("-*") and - p not in r_keywords and - p not in ['setProps', 'dashEvents', 'fireEvent'] ) file_path = os.path.join('man', file_name) @@ -298,7 +300,6 @@ def write_class_file(name, # we may eventually be able to generate similar documentation using # doxygen and an R plugin, but for now we'll just do it on our own # from within Python - # noqa E344 write_help_file( name, props, From c80e33e7fee21ce5c2e228f46cee3261c1751871 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 15:08:01 -0500 Subject: [PATCH 37/55] :paw_prints: migrated long strings to globals for readability --- dash/development/_r_components_generation.py | 94 ++++++++++---------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 060090100a..34b4b62e9f 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -47,7 +47,7 @@ file = "lib/"), meta = NULL, script = "{dep_rpp}", stylesheet = NULL, head = NULL, attachment = NULL, package = "{rpkgname}", -all_files = FALSE), class = "html_dependency")''' +all_files = FALSE), class = "html_dependency")''' # noqa:E501 frame_close_template = ''') return(deps_metadata) @@ -68,6 +68,52 @@ }} ''' +description_string = '''Package: {package_name} +Title: {package_description} +Version: {package_version} +Authors @R: as.person(c({package_author})) +Description: {package_description} +Suggests: testthat, roxygen2 +License: {package_license} +URL: {package_url} +BugReports: {package_issues} +Encoding: UTF-8 +LazyData: true +Author: {package_author_no_email} +Maintainer: {package_author} +''' + +rbuild_ignore_string = r'''# ignore JS config files/folders +node_modules/ +coverage/ +src/ +lib/ +.babelrc +.builderrc +.eslintrc +.npmignore + +# demo folder has special meaning in R +# this should hopefully make it still +# allow for the possibility to make R demos +demo/*.js +demo/*.html +demo/*.css + +# ignore python files/folders +setup.py +usage.py +setup.py +requirements.txt +MANIFEST.in +CHANGELOG.md +test/ +# CRAN has weird LICENSE requirements +LICENSE.txt +^.*\.Rproj$ +^\.Rproj\.user$ +''' + # This is an initial attempt at resolving type inconsistencies # between R and JSON. @@ -189,6 +235,7 @@ def generate_js_metadata(project_shortname): function_frame_open = frame_open_template.format(rpkgname=rpkgname) function_frame = [] + function_frame_body = [] # pylint: disable=consider-using-enumerate if len(jsdist) > 1: @@ -421,21 +468,6 @@ def generate_rpkg(pkg_data, import_string =\ '# AUTO GENERATED FILE - DO NOT EDIT\n\n' - description_string = '''Package: {package_name} -Title: {package_description} -Version: {package_version} -Authors @R: as.person(c({package_author})) -Description: {package_description} -Suggests: testthat, roxygen2 -License: {package_license} -URL: {package_url} -BugReports: {package_issues} -Encoding: UTF-8 -LazyData: true -Author: {package_author_no_email} -Maintainer: {package_author} -''' - description_string = description_string.format( package_name=package_name, package_description=package_description, @@ -447,36 +479,6 @@ def generate_rpkg(pkg_data, package_author_no_email=package_author_no_email ) - rbuild_ignore_string = r'''# ignore JS config files/folders -node_modules/ -coverage/ -src/ -lib/ -.babelrc -.builderrc -.eslintrc -.npmignore - -# demo folder has special meaning in R -# this should hopefully make it still -# allow for the possibility to make R demos -demo/*.js -demo/*.html -demo/*.css - -# ignore python files/folders -setup.py -usage.py -setup.py -requirements.txt -MANIFEST.in -CHANGELOG.md -test/ -# CRAN has weird LICENSE requirements -LICENSE.txt -^.*\.Rproj$ -^\.Rproj\.user$ -''' # generate the internal (not exported to the user) functions which # supply the JavaScript dependencies to the htmlDependency package, # which is required by DashR (this avoids having to generate an From c150dd2a3cdb3f4acdf06dcaedfd48516d176d70 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 12 Dec 2018 15:15:43 -0500 Subject: [PATCH 38/55] resolve pylint complaints --- dash/development/_r_components_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 34b4b62e9f..487cc6939e 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -68,7 +68,7 @@ }} ''' -description_string = '''Package: {package_name} +description_template = '''Package: {package_name} Title: {package_description} Version: {package_version} Authors @R: as.person(c({package_author})) @@ -468,7 +468,7 @@ def generate_rpkg(pkg_data, import_string =\ '# AUTO GENERATED FILE - DO NOT EDIT\n\n' - description_string = description_string.format( + description_string = description_template.format( package_name=package_name, package_description=package_description, package_version=package_version, From 14d23c298b81dc01256163db39549f9e8abe2704 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Thu, 13 Dec 2018 18:20:37 -0500 Subject: [PATCH 39/55] addresses T4rk1n comment about taking `name` attribute --- dash/development/_r_components_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 487cc6939e..c1d7f425af 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -118,13 +118,13 @@ # This is an initial attempt at resolving type inconsistencies # between R and JSON. def json_to_r_type(current_prop): - object_type = current_prop['type'].values() + object_type = current_prop['type']['name'] if 'defaultValue' in current_prop and object_type == 'string': if "\"" in current_prop['defaultValue']['value']: argument = current_prop['defaultValue']['value'] else: argument = "'{}'".format(current_prop['defaultValue']['value']) - elif 'defaultValue' in current_prop and object_type == ['object']: + elif 'defaultValue' in current_prop and object_type == 'object': argument = 'list()' elif 'defaultValue' in current_prop and \ current_prop['defaultValue']['value'] == '[]': From 8332e0c370fb1e201eff0bb3c1f09f5f64724d93 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Fri, 14 Dec 2018 10:33:36 -0500 Subject: [PATCH 40/55] fixed missing quotation in propNames removed after earlier edits --- dash/development/_r_components_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index c1d7f425af..6cc4e00c31 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -147,7 +147,7 @@ def generate_class_string(name, props, project_shortname, prefix): # Produce a string with all property names other than WCs prop_names = ", ".join( - "{}".format(p) + '\'{}\''.format(p) for p in prop_keys if '*' not in p and p not in ['setProps', 'dashEvents', 'fireEvent'] @@ -432,7 +432,7 @@ def generate_rpkg(pkg_data, package_name = snake_case_to_camel_case(project_shortname) package_description = pkg_data.get('description', '') - package_version = pkg_data.get('version', '0.01') + package_version = pkg_data.get('version', '0.0.1') if 'bugs' in pkg_data.keys(): package_issues = pkg_data['bugs'].get('url', '') From 47e851610f8592d83830dc6985d3367231979afa Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 15:52:07 -0500 Subject: [PATCH 41/55] fixed props, renamed/improved props_to_r method --- dash/development/_r_components_generation.py | 22 +++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 6cc4e00c31..b746fc51d3 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -123,7 +123,23 @@ def json_to_r_type(current_prop): if "\"" in current_prop['defaultValue']['value']: argument = current_prop['defaultValue']['value'] else: - argument = "'{}'".format(current_prop['defaultValue']['value']) + argument = "{}".format(current_prop['defaultValue']['value']) + elif object_type == 'custom' and 'raw' in current_prop['type']: + argument = current_prop['defaultValue'].get('value', 'numeric()') + elif object_type == 'enum': + val = current_prop['type']['value'][0]['value'] + # Check if val is a string which can be cast to an int + try: + int(val) + argument = 'integer()' + # Check if val is a string which can be cast to a float, if not int + except ValueError: + try: + float(val) + argument = 'numeric()' + # If val cannot be cast to int nor float, assume string + except ValueError: + argument = 'character()' elif 'defaultValue' in current_prop and object_type == 'object': argument = 'list()' elif 'defaultValue' in current_prop and \ @@ -180,8 +196,8 @@ def generate_class_string(name, props, project_shortname, prefix): for p in prop_keys ) - if 'children' in props: - prop_keys.remove('children') + # if 'children' in props: + # prop_keys.remove('children') # pylint: disable=C0301 default_paramtext += ", ".join( From dc8b254e375895b9a56df7ea6140e2fe276053f9 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 15:52:48 -0500 Subject: [PATCH 42/55] removed comment --- dash/development/_r_components_generation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index b746fc51d3..ff69270920 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -196,9 +196,6 @@ def generate_class_string(name, props, project_shortname, prefix): for p in prop_keys ) - # if 'children' in props: - # prop_keys.remove('children') - # pylint: disable=C0301 default_paramtext += ", ".join( '{}={}'.format(p, p) From 059400f4f8536a5a5524e20c504b3161eeba7c13 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 16:01:45 -0500 Subject: [PATCH 43/55] jk, method *now* json_to_r_type >> props_to_r_type --- dash/development/_r_components_generation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index ff69270920..2add7eb510 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -117,7 +117,7 @@ # This is an initial attempt at resolving type inconsistencies # between R and JSON. -def json_to_r_type(current_prop): +def props_to_r_type(current_prop): object_type = current_prop['type']['name'] if 'defaultValue' in current_prop and object_type == 'string': if "\"" in current_prop['defaultValue']['value']: @@ -190,7 +190,7 @@ def generate_class_string(name, props, project_shortname, prefix): prop_keys.remove(p) default_argtext += ", ".join( - '{}={}'.format(p, json_to_r_type(props[p])) + '{}={}'.format(p, props_to_r_type(props[p])) if 'defaultValue' in props[p] else '{}=NULL'.format(p) for p in prop_keys @@ -313,7 +313,7 @@ def write_help_file(name, props, description, prefix): prop_keys.remove(p) default_argtext += ", ".join( - '{}={}'.format(p, json_to_r_type(props[p])) + '{}={}'.format(p, props_to_r_type(props[p])) if 'defaultValue' in props[p] else '{}=NULL'.format(p) for p in prop_keys From 0fc3d2d7b1573dbc11f2e2b6b7d815cc161b7071 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 16:18:57 -0500 Subject: [PATCH 44/55] make warning msg for component prefix more explicit --- dash/development/component_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 0b6112ead8..cbc7b89e13 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -43,7 +43,7 @@ def generate_components(components_source, project_shortname, ] prefix = s[-1] print( - 'Warning: a component prefix was ' + 'Warning: a DashR component prefix was ' 'not provided. Using `{}`.'.format(prefix), file=sys.stderr ) From 3176684dc64fa7bcb0d8396d08562ae9a06da774 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 16:29:54 -0500 Subject: [PATCH 45/55] edited to remove use of __contains__ --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 2add7eb510..f62fa617b0 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -253,7 +253,7 @@ def generate_js_metadata(project_shortname): # pylint: disable=consider-using-enumerate if len(jsdist) > 1: for dep in range(len(jsdist)): - if jsdist[dep]['relative_package_path'].__contains__('dash_'): + if 'dash_' in jsdist[dep]['relative_package_path']: dep_name = jsdist[dep]['relative_package_path'].split('.')[0] else: dep_name = '{}_{}'.format(project_shortname, str(dep)) From f8041a7a624ab204817a97f9df9bbc7c66740961 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 16:33:02 -0500 Subject: [PATCH 46/55] remove unnecessary call to `os.makedirs` --- dash/development/_r_components_generation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index f62fa617b0..50cc824d13 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -266,7 +266,7 @@ def generate_js_metadata(project_shortname): dep_rpp=jsdist[dep]['relative_package_path'] )] function_frame_body = ',\n'.join(function_frame) - elif len(jsdist) == 1: + else len(jsdist) == 1: function_frame_body = frame_body_template. \ format(project_shortname=project_shortname, project_ver=project_ver, @@ -409,9 +409,6 @@ def write_js_metadata(project_shortname): # now copy over all JS dependencies from the (Python) components dir # the inst/lib directory for the package won't exist on first call # create this directory if it is missing - if not os.path.exists('inst'): - os.makedirs('inst') - if not os.path.exists('inst/lib'): os.makedirs('inst/lib') From df97a38a28cc4ce381cfd37c242c4e729c7924e1 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 16:42:32 -0500 Subject: [PATCH 47/55] inserted more detailed docstring for help file generator --- dash/development/_r_components_generation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 50cc824d13..9450c039e9 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -286,14 +286,14 @@ def write_help_file(name, props, description, prefix): Parameters ---------- - name - props - description - prefix + name = the name of the Dash component for which a help file is generated + props = the properties of the component + description = the component's description, inserted into help file header + prefix = the DashR library prefix (optional, can be a blank string) Returns ------- - + writes an R help file to the man directory for the generated R package """ file_name = '{}{}.Rd'.format(prefix, name) From adcddac5d7be490c817f84f9bc2e98c1e85d6221 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 17 Dec 2018 16:44:31 -0500 Subject: [PATCH 48/55] fix up docstrings for `generate_js_metadata` --- dash/development/_r_components_generation.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 9450c039e9..017f888ada 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -221,15 +221,13 @@ def generate_js_metadata(project_shortname): dependency information required by htmltools package, which is loaded by dashR. - Inspired by http://jameso.be/2013/08/06/namedtuple.html - Parameters ---------- - project_shortname + project_shortname = component library name, in snake case Returns ------- - function_string + function_string = complete R function code to provide component features """ # import component library module into sys From 4ca4d58490001e88e3ebbc8529c4841199688bc7 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 11:21:30 -0500 Subject: [PATCH 49/55] removed dead function block --- dash/development/_r_components_generation.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 017f888ada..e633ef4455 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -368,14 +368,6 @@ def write_class_file(name, print('Generated {}'.format(file_name)) -# pylint: disable=inconsistent-return-statements -def generate_export_string(name, prefix): - if not name.endswith('-*') and \ - str(name) not in r_keywords and \ - str(name) not in ['setProps', 'children', 'dashEvents']: - return 'export({}{})\n'.format(prefix, name) - - def write_js_metadata(project_shortname): """ Write an internal (not exported) R function to return all JS From f7d02dc1c7733946ea89dddd6515051331bfef7d Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 11:23:07 -0500 Subject: [PATCH 50/55] converted else back to elif --- dash/development/_r_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index e633ef4455..d8e9c52fcd 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -264,7 +264,7 @@ def generate_js_metadata(project_shortname): dep_rpp=jsdist[dep]['relative_package_path'] )] function_frame_body = ',\n'.join(function_frame) - else len(jsdist) == 1: + elif len(jsdist) == 1: function_frame_body = frame_body_template. \ format(project_shortname=project_shortname, project_ver=project_ver, From cdf8f9c4590a6ad9e5fe47be2b8029d78d8b9a12 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 13:09:50 -0500 Subject: [PATCH 51/55] updated `props_to_r_type` --- dash/development/_r_components_generation.py | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index d8e9c52fcd..f1097c0e6e 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -127,24 +127,20 @@ def props_to_r_type(current_prop): elif object_type == 'custom' and 'raw' in current_prop['type']: argument = current_prop['defaultValue'].get('value', 'numeric()') elif object_type == 'enum': - val = current_prop['type']['value'][0]['value'] - # Check if val is a string which can be cast to an int - try: - int(val) - argument = 'integer()' - # Check if val is a string which can be cast to a float, if not int - except ValueError: - try: - float(val) - argument = 'numeric()' - # If val cannot be cast to int nor float, assume string - except ValueError: - argument = 'character()' + argument = current_prop.get('defaultValue', {}).get('value', 'NULL') elif 'defaultValue' in current_prop and object_type == 'object': argument = 'list()' elif 'defaultValue' in current_prop and \ current_prop['defaultValue']['value'] == '[]': argument = 'list()' + elif object_type == 'number': + argument = current_prop['defaultValue'].get('value', 'NULL') + elif object_type == 'bool': + argument = current_prop['defaultValue'].get('value') + if argument: + argument = 'TRUE' + else: + argument = 'logical()' else: argument = 'NULL' return argument From 4a67a6cb8ef2f8f650b3647e30425d6a9db2804e Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 13:15:58 -0500 Subject: [PATCH 52/55] building R pkg now requires setting `--r-prefix` --- dash/development/component_generator.py | 26 ++++--------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index cbc7b89e13..733c412594 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -27,7 +27,6 @@ class _CombinedFormatter(argparse.ArgumentDefaultsHelpFormatter, # pylint: disable=too-many-locals def generate_components(components_source, project_shortname, package_info_filename='package.json', - generate_r_components=False, rprefix=None): project_shortname = project_shortname.replace('-', '_').rstrip('/\\') @@ -36,17 +35,6 @@ def generate_components(components_source, project_shortname, if rprefix: prefix = rprefix - else: - s = [ - x for x in project_shortname.split('_') - if x not in ('dash', 'component', 'components') - ] - prefix = s[-1] - print( - 'Warning: a DashR component prefix was ' - 'not provided. Using `{}`.'.format(prefix), - file=sys.stderr - ) is_windows = sys.platform == 'win32' @@ -79,7 +67,7 @@ def generate_components(components_source, project_shortname, metadata = json.loads(out.decode()) generator_methods = [generate_class_file] - if generate_r_components: + if rprefix: if not os.path.exists('man'): os.makedirs('man') if not os.path.exists('R'): @@ -98,7 +86,7 @@ def generate_components(components_source, project_shortname, generate_imports(project_shortname, components) - if generate_r_components: + if rprefix: with open('package.json', 'r') as f: pkg_data = json.load(f) @@ -125,21 +113,15 @@ def cli(): default='package.json', help='The filename of the copied `package.json` to `project_shortname`' ) - parser.add_argument( - '-r', '--rlang', - action='store_true', - help='Experimental: write DashR components to R dir, create R package' - ) parser.add_argument( '--r-prefix', - help='Inserts a prefix string that will be prepended to DashR' - 'component names at generation time.' + help='Experimental: specify a prefix for DashR component names, write' + 'DashR components to R dir, create R package.' ) args = parser.parse_args() generate_components(args.components_source, args.project_shortname, package_info_filename=args.package_info_filename, - generate_r_components=args.rlang, rprefix=args.r_prefix) From 99b6438b7878daf6a36a059d84f79edebfeb8082 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 13:40:41 -0500 Subject: [PATCH 53/55] :paw_prints: relocated `importlib` statements --- dash/development/_r_components_generation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index f1097c0e6e..b18b8e8ef2 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -5,6 +5,7 @@ import sys import shutil import glob +import importlib from ._all_keywords import r_keywords from ._py_components_generation import reorder_props @@ -225,6 +226,7 @@ def generate_js_metadata(project_shortname): ------- function_string = complete R function code to provide component features """ + importlib.import_module(project_shortname) # import component library module into sys mod = sys.modules[project_shortname] From 28725e6fdee3be2713b1910cb207ce38e13d757e Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 14:20:55 -0500 Subject: [PATCH 54/55] removed unnecessary importlib import --- dash/development/component_generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 1e01e39146..5e18cf04e8 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -7,7 +7,6 @@ import os import argparse import shutil -import importlib import functools import pkg_resources From a073828b208a940772303d61de3fa044f0ddb9bd Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 18 Dec 2018 14:23:50 -0500 Subject: [PATCH 55/55] removed trailing whitespace --- dash/development/component_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 5e18cf04e8..fe3c82d1cf 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -28,7 +28,7 @@ def generate_components(components_source, project_shortname, package_info_filename='package.json', ignore='^_', rprefix=None): - + project_shortname = project_shortname.replace('-', '_').rstrip('/\\') if rprefix: @@ -123,7 +123,7 @@ def cli(): help='Experimental: specify a prefix for DashR component names, write' 'DashR components to R dir, create R package.' ) - + args = parser.parse_args() generate_components( args.components_source, args.project_shortname,