From 50ddc9961a16f1dbe77cb4ed4a8a7b4175764785 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Fri, 10 Apr 2020 12:36:25 -0400 Subject: [PATCH 01/18] add Julia keywords --- dash/development/_all_keywords.py | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/dash/development/_all_keywords.py b/dash/development/_all_keywords.py index 9279138ca9..7d3e5877c4 100644 --- a/dash/development/_all_keywords.py +++ b/dash/development/_all_keywords.py @@ -68,3 +68,38 @@ "NA_character_", "...", } + +# This is a set of Julia reserved words that cannot be used as function +# argument names. + +julia_keywords = { + "baremodule", + "begin", + "break", + "catch", + "const", + "continue", + "do", + "else", + "elseif", + "end", + "export", + "false", + "finally", + "for", + "function", + "global", + "if", + "import", + "let", + "local", + "macro", + "module", + "quote", + "return", + "struct", + "true", + "try", + "using", + "while", +} From 4df1a7e59e048140e9b5e48f54b5b7f746185d9f Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 15 Apr 2020 00:52:38 -0400 Subject: [PATCH 02/18] :sparkles: initial Julia component generator --- dash/development/_jl_components_generation.py | 400 ++++++++++++++++++ dash/development/component_generator.py | 22 + 2 files changed, 422 insertions(+) create mode 100644 dash/development/_jl_components_generation.py diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py new file mode 100644 index 0000000000..ddf6cc74d2 --- /dev/null +++ b/dash/development/_jl_components_generation.py @@ -0,0 +1,400 @@ +from __future__ import absolute_import +from __future__ import print_function + +import copy +import os +import shutil +import glob +import warnings + +from ._all_keywords import julia_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 +jl_component_string = ''' +""" + {funcname}(;kwargs...) + {funcname}(children::Any;kwargs...) + {funcname}(children_maker::Function;kwargs...) + +{docstring} +""" +function {funcname}(; kwargs...) + available_props = Set(Symbol[{component_props}]) + wild_props = Set(Symbol[{wildcard_symbols}]) + wild_regs = r"^(?{wildcard_names})" + + result = Component("{element_name}", "{module_name}", Dict{{Symbol, Any}}(), available_props, Set(Symbol[{wildcard_symbols}])) + + for (prop, value) = pairs(kwargs) + m = match(wild_regs, string(prop)) + if (length(wild_props) == 0 || isnothing(m)) && !(prop in available_props) + throw(ArgumentError("Invalid property $(string(prop)) for component " * "{funcname}")) + end + + push!(result.props, prop => Front.to_dash(value)) + end + + return result +end + +function {funcname}(children::Any; kwargs...) + result = {funcname}(;kwargs...) + push!(result.props, :children => Front.to_dash(children)) + return result +end + +{funcname}(children_maker::Function; kwargs...) = {funcname}(children_maker(); kwargs...) +''' # noqa:E501 + + +def stringify_wildcards(wclist, no_symbol=False): + if no_symbol: + wcstring = "|".join( + '{}-'.format(item) for item in wclist + ) + else: + wcstring = ", ".join( + 'Symbol("{}-")'.format(item) for item in wclist + ) + return(wcstring) + + +def get_wildcards_jl(props): + prop_keys = list(props.keys()) + wildcards = [] + for key in prop_keys: + if key.endswith("-*"): + wildcards.append(key.replace("-*", "")) + return(wildcards) + + +def get_jl_prop_types(type_object): + """Mapping from the PropTypes js type object to the Julia type.""" + + def shape_or_exact(): + return "lists containing elements {}.\n{}".format( + ", ".join("'{}'".format(t) for t in list(type_object["value"].keys())), + "Those elements have the following types:\n{}".format( + "\n".join( + create_prop_docstring_jl( + prop_name=prop_name, + type_object=prop, + required=prop["required"], + description=prop.get("description", ""), + indent_num=1, + ) + for prop_name, prop in list(type_object["value"].items()) + ) + ), + ) + + return dict( + array=lambda: "Array", + bool=lambda: "Bool", + number=lambda: "Float64", + string=lambda: "String", + object=lambda: "Dict", + any=lambda: "Bool | Float64 | String | Dict | Array", + element=lambda: "dash component", + node=lambda: "a list of or a singular dash component, string or number", + # React's PropTypes.oneOf + enum=lambda: "a value equal to: {}".format( + ", ".join("{}".format(str(t["value"])) for t in type_object["value"]) + ), + # React's PropTypes.oneOfType + union=lambda: "{}".format( + " | ".join( + "{}".format(get_jl_type(subType)) + for subType in type_object["value"] + if get_jl_type(subType) != "" + ) + ), + # React's PropTypes.arrayOf + arrayOf=lambda: ( + "Array" + + ( + " of {}s".format(get_jl_type(type_object["value"])) + if get_jl_type(type_object["value"]) != "" + else "" + ) + ), + # React's PropTypes.objectOf + objectOf=lambda: ("Dict with Strings as keys and values of type {}").format( + get_jl_type(type_object["value"]) + ), + # React's PropTypes.shape + shape=shape_or_exact, + # React's PropTypes.exact + exact=shape_or_exact, + ) + + +def filter_props(props): + """Filter props from the Component arguments to exclude: + - Those without a "type" or a "flowType" field + - Those with arg.type.name in {'func', 'symbol', 'instanceOf'} + Parameters + ---------- + props: dict + Dictionary with {propName: propMetadata} structure + Returns + ------- + dict + Filtered dictionary with {propName: propMetadata} structure + """ + filtered_props = copy.deepcopy(props) + + for arg_name, arg in list(filtered_props.items()): + if "type" not in arg and "flowType" not in arg: + filtered_props.pop(arg_name) + continue + + # Filter out functions and instances -- + if "type" in arg: # These come from PropTypes + arg_type = arg["type"]["name"] + if arg_type in {"func", "symbol", "instanceOf"}: + filtered_props.pop(arg_name) + elif "flowType" in arg: # These come from Flow & handled differently + arg_type_name = arg["flowType"]["name"] + if arg_type_name == "signature": + # This does the same as the PropTypes filter above, but "func" + # is under "type" if "name" is "signature" vs just in "name" + if "type" not in arg["flowType"] or arg["flowType"]["type"] != "object": + filtered_props.pop(arg_name) + else: + raise ValueError + + return filtered_props + + +def get_jl_type(type_object): + """ + Convert JS types to Julia types for the component definition + Parameters + ---------- + type_object: dict + react-docgen-generated prop type dictionary + Returns + ------- + str + Julia type string + """ + js_type_name = type_object["name"] + js_to_jl_types = get_jl_prop_types(type_object=type_object) + if ( + "computed" in type_object + and type_object["computed"] + or type_object.get("type", "") == "function" + ): + return "" + elif js_type_name in js_to_jl_types: + prop_type = js_to_jl_types[js_type_name]() + return prop_type + return "" + + +def print_jl_type(typedata): + typestring = get_jl_type(typedata).capitalize() + if typestring: + typestring += ". " + return typestring + + +def create_docstring_jl(component_name, props, description): + """Create the Dash component docstring. + Parameters + ---------- + component_name: str + Component name + props: dict + Dictionary with {propName: propMetadata} structure + description: str + Component description + Returns + ------- + str + Dash component docstring + """ + # Ensure props are ordered with children first + props = reorder_props(props=props) + + return ( + """A{n} {name} component.\n{description} +Keyword arguments:\n{args}""" + ).format( + n="n" if component_name[0].lower() in ["a", "e", "i", "o", "u"] else "", + name=component_name, + description=description, + args="\n".join( + create_prop_docstring_jl( + prop_name=p, + type_object=prop["type"] if "type" in prop else prop["flowType"], + required=prop["required"], + description=prop["description"], + indent_num=0, + ) + for p, prop in list(filter_props(props).items()) + ), + ) + + +# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments +def create_prop_docstring_jl( + prop_name, + type_object, + required, + description, + indent_num, +): + """ + Create the Dash component prop docstring + Parameters + ---------- + prop_name: str + Name of the Dash component prop + type_object: dict + react-docgen-generated prop type dictionary + required: bool + Component is required? + description: str + Dash component description + indent_num: int + Number of indents to use for the context block + (creates 2 spaces for every indent) + is_flow_type: bool + Does the prop use Flow types? Otherwise, uses PropTypes + Returns + ------- + str + Dash component prop docstring + """ + jl_type_name = get_jl_type(type_object=type_object) + + indent_spacing = " " * indent_num + if "\n" in jl_type_name: + return ( + "{indent_spacing}- `{name}` ({is_required}): {description}. " + "{name} has the following type: {type}".format( + indent_spacing=indent_spacing, + name=prop_name, + type=jl_type_name, + description=description, + is_required="required" if required else "optional", + ) + ) + return "{indent_spacing}- `{name}` ({type}{is_required}){description}".format( + indent_spacing=indent_spacing, + name=prop_name, + type="{}; ".format(jl_type_name) if jl_type_name else "", + description=(": {}".format(description) if description != "" else ""), + is_required="required" if required else "optional", + ) + + +# this logic will permit passing blank Julia prefixes to +# dash-generate-components, while also enforcing +# lower case names for the resulting functions; if a prefix +# is supplied, leave it as-is +def format_fn_name(prefix, name): + if prefix: + return "{}_{}".format(prefix, name.lower()) + return name.lower() + + +def generate_class_string(name, props, description, project_shortname, prefix): + # Ensure props are ordered with children first + filtered_props = reorder_props(filter_props(props)) + + prop_keys = list(filtered_props.keys()) + + docstring = create_docstring_jl( + component_name=name, props=filtered_props, description=description + ).replace("\r\n", "\n") + + wclist = get_wildcards_jl(props) + default_paramtext = "" + + # Filter props to remove those we don't want to expose + for item in prop_keys[:]: + if item.endswith("-*") or item == "setProps": + prop_keys.remove(item) + elif item in julia_keywords: + prop_keys.remove(item) + warnings.warn( + ( + 'WARNING: prop "{}" in component "{}" is a Julia keyword' + " - REMOVED FROM THE JULIA COMPONENT" + ).format(item, name) + ) + + default_paramtext += ", ".join( + ":{}".format(p) + for p in prop_keys + ) + + return jl_component_string.format( + funcname=format_fn_name(prefix, name), + docstring=docstring, + component_props=default_paramtext, + wildcard_symbols=stringify_wildcards(wclist, no_symbol=False), + wildcard_names=stringify_wildcards(wclist, no_symbol=True), + element_name=name, + module_name=project_shortname, + ) + + +def generate_struct_file( + name, props, description, project_shortname, prefix +): + props = reorder_props(props=props) + import_string = "# AUTO GENERATED FILE - DO NOT EDIT\n" + class_string = generate_class_string(name, + props, + description, + project_shortname, + prefix) + + file_name = format_fn_name(prefix, name) + ".jl" + + file_path = os.path.join("src", file_name) + with open(file_path, "w") as f: + f.write(import_string) + f.write(class_string) + + print("Generated {}".format(file_name)) + + +# pylint: disable=unused-argument +def generate_module( + project_shortname, + components, + metadata, + prefix, + **kwargs +): + # the Julia source directory for the package won't exist on first call + # create the Julia directory if it is missing + if not os.path.exists("src"): + os.makedirs("src") + + # 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 os.path.exists("deps"): + shutil.rmtree("deps") + + os.makedirs("deps") + + for javascript in glob.glob("{}/*.js".format(project_shortname)): + shutil.copy(javascript, "deps/") + + for css in glob.glob("{}/*.css".format(project_shortname)): + shutil.copy(css, "deps/") + + for sourcemap in glob.glob("{}/*.map".format(project_shortname)): + shutil.copy(sourcemap, "deps/") diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index fd1c1d62ec..6ddc64db05 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -18,6 +18,8 @@ from ._py_components_generation import generate_class_file from ._py_components_generation import generate_imports from ._py_components_generation import generate_classes_files +from ._jl_components_generation import generate_struct_file +from ._jl_components_generation import generate_module reserved_words = [ @@ -46,6 +48,7 @@ def generate_components( rdepends="", rimports="", rsuggests="", + jlprefix=None, ): project_shortname = project_shortname.replace("-", "_").rstrip("/\\") @@ -107,6 +110,11 @@ def generate_components( functools.partial(write_class_file, prefix=rprefix, rpkg_data=rpkg_data) ) + if jlprefix is not False: + generator_methods.append( + functools.partial(generate_struct_file, prefix=jlprefix) + ) + components = generate_classes_files(project_shortname, metadata, *generator_methods) with open(os.path.join(project_shortname, "metadata.json"), "w") as f: @@ -127,6 +135,14 @@ def generate_components( rsuggests, ) + if jlprefix is not False: + generate_module( + project_shortname, + components, + metadata, + jlprefix + ) + def safe_json_loads(s): jsondata_unicode = json.loads(s, object_pairs_hook=OrderedDict) @@ -181,6 +197,11 @@ def cli(): help="Specify a comma-separated list of R packages to be " "inserted into the Suggests field of the DESCRIPTION file.", ) + parser.add_argument( + "--jl-prefix", + help="Specify a prefix for Dash for R component names, write " + "components to R dir, create R package.", + ) args = parser.parse_args() generate_components( @@ -192,6 +213,7 @@ def cli(): rdepends=args.r_depends, rimports=args.r_imports, rsuggests=args.r_suggests, + jlprefix=args.jl_prefix, ) From 39932891630ae9cba59bb31099b51a970eda7bff Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Wed, 15 Apr 2020 01:11:08 -0400 Subject: [PATCH 03/18] :necktie: fix silly paren lint err --- dash/development/_jl_components_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index ddf6cc74d2..7b45a394aa 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -60,7 +60,7 @@ def stringify_wildcards(wclist, no_symbol=False): wcstring = ", ".join( 'Symbol("{}-")'.format(item) for item in wclist ) - return(wcstring) + return wcstring def get_wildcards_jl(props): @@ -69,7 +69,7 @@ def get_wildcards_jl(props): for key in prop_keys: if key.endswith("-*"): wildcards.append(key.replace("-*", "")) - return(wildcards) + return wildcards def get_jl_prop_types(type_object): From 490e8493f511fc7bb89d6aa770ce0fe8ae19d071 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Fri, 15 May 2020 20:26:54 +0300 Subject: [PATCH 04/18] Julia packge generator --- dash/development/_jl_components_generation.py | 128 ++++++++++++++++++ dash/development/component_generator.py | 5 + 2 files changed, 133 insertions(+) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 7b45a394aa..dec24323bf 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -6,15 +6,22 @@ import shutil import glob import warnings +import sys +import importlib +import uuid from ._all_keywords import julia_keywords from ._py_components_generation import reorder_props +#uuid of Dash Julia package. Used as base for component package uuid +jl_dash_uuid = "1b08a953-4be3-4667-9a23-3db579824955" # Declaring longer string templates as globals to improve # readability, make method logic clearer to anyone inspecting # code below jl_component_string = ''' +export {funcname} + """ {funcname}(;kwargs...) {funcname}(children::Any;kwargs...) @@ -50,6 +57,57 @@ {funcname}(children_maker::Function; kwargs...) = {funcname}(children_maker(); kwargs...) ''' # noqa:E501 +jl_package_file_string = ''' +module {package_name} +using Dash + +const resources_path = realpath(joinpath( @__DIR__, "..", "deps")) +const version = "{version}" + +{component_includes} + +function __init__() + Dash.register_package( + Dash.ResourcePkg( + "{project_shortname}", + resources_path, + version = version, + [ + {resources_dist} + ] + ) + ) +end +end +''' + +jl_projecttoml_string = ''' +name = "{package_name}" +uuid = "{package_uuid}" +{authors} +version = "{version}" + +[deps] +Dash = "{dash_uuid}" + +[compact] +julia = "1.1" +Dash = ">=0.1" +''' + +jl_component_include_string = 'include("{name}.jl")' + +jl_resource_tuple_string = '''Dash.Resource( + relative_package_path = {relative_package_path}, + external_url = {external_url}, + dynamic = {dynamic}, + async = {async_string}, + type = :{type} +)''' + +def jl_package_name(namestring): + s = namestring.split("_") + return "".join(w.capitalize() for w in s) def stringify_wildcards(wclist, no_symbol=False): if no_symbol: @@ -306,6 +364,69 @@ def format_fn_name(prefix, name): return name.lower() +def generate_metadata_strings(resources, type): + def noting_or_string(v): + return '"{}"'.format(v) if v else "nothing" + return [jl_resource_tuple_string.format( + relative_package_path = noting_or_string(resource.get("relative_package_path", "")), + external_url = noting_or_string(resource.get("external_url", "")), + dynamic = str(resource.get("dynamic", 'nothing')).lower(), + type = type, + async_string = ":{}".format(str(resource.get("async")).lower()) + if "async" in resource.keys() + else 'nothing' + ) for resource in resources] + + +def generate_package_file(project_shortname, components, pkg_data, prefix): + package_name = jl_package_name(project_shortname) + + sys.path.insert(0, os.getcwd()) + mod = importlib.import_module(project_shortname) + js_dist = getattr(mod, "_js_dist", []) + css_dist = getattr(mod, "_css_dist", []) + project_ver = pkg_data.get("version") + + resources_dist = ",\n".join( + generate_metadata_strings(js_dist, "js") + generate_metadata_strings(css_dist, "css") + ) + + package_string = jl_package_file_string.format( + package_name = package_name, + component_includes = "\n".join( + [jl_component_include_string.format(name = format_fn_name(prefix, comp_name)) for comp_name in components] + ), + resources_dist = resources_dist, + version = project_ver, + project_shortname = project_shortname + + ) + file_path = os.path.join("src", package_name + ".jl") + with open(file_path, "w") as f: + f.write(package_string) + print("Generated {}".format(file_path)) + +def generate_toml_file(project_shortname, pkg_data): + package_author = pkg_data.get("author", "") + project_ver = pkg_data.get("version") + package_name = jl_package_name(project_shortname) + u = uuid.UUID(jl_dash_uuid) + package_uuid = uuid.UUID(hex=u.hex[:-12] + hex(hash(package_name))[-12:]) + + authors_string = 'authors = ["{}"]'.format(package_author) if package_author else "" + + toml_string = jl_projecttoml_string.format( + package_name = package_name, + package_uuid = package_uuid, + version = project_ver, + authors = authors_string, + dash_uuid = jl_dash_uuid + ) + file_path = "Project.toml" + with open(file_path, "w") as f: + f.write(toml_string) + print("Generated {}".format(file_path)) + def generate_class_string(name, props, description, project_shortname, prefix): # Ensure props are ordered with children first filtered_props = reorder_props(filter_props(props)) @@ -374,6 +495,7 @@ def generate_module( project_shortname, components, metadata, + pkg_data, prefix, **kwargs ): @@ -398,3 +520,9 @@ def generate_module( for sourcemap in glob.glob("{}/*.map".format(project_shortname)): shutil.copy(sourcemap, "deps/") + + generate_package_file(project_shortname, components, pkg_data, prefix) + generate_toml_file(project_shortname, pkg_data) + + + diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 6ddc64db05..9512b8864a 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -111,6 +111,10 @@ def generate_components( ) if jlprefix is not False: + if pkg_data is None: + with open("package.json", "r") as f: + pkg_data = safe_json_loads(f.read()) + generator_methods.append( functools.partial(generate_struct_file, prefix=jlprefix) ) @@ -140,6 +144,7 @@ def generate_components( project_shortname, components, metadata, + pkg_data, jlprefix ) From d771176517af41649ad54a1b22e388b2e2169bec Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Mon, 18 May 2020 07:18:47 +0300 Subject: [PATCH 05/18] Generate Project.toml for Dash.jl components (#1253) --- dash/development/_jl_components_generation.py | 128 ++++++++++++++++++ dash/development/component_generator.py | 5 + 2 files changed, 133 insertions(+) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 7b45a394aa..dec24323bf 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -6,15 +6,22 @@ import shutil import glob import warnings +import sys +import importlib +import uuid from ._all_keywords import julia_keywords from ._py_components_generation import reorder_props +#uuid of Dash Julia package. Used as base for component package uuid +jl_dash_uuid = "1b08a953-4be3-4667-9a23-3db579824955" # Declaring longer string templates as globals to improve # readability, make method logic clearer to anyone inspecting # code below jl_component_string = ''' +export {funcname} + """ {funcname}(;kwargs...) {funcname}(children::Any;kwargs...) @@ -50,6 +57,57 @@ {funcname}(children_maker::Function; kwargs...) = {funcname}(children_maker(); kwargs...) ''' # noqa:E501 +jl_package_file_string = ''' +module {package_name} +using Dash + +const resources_path = realpath(joinpath( @__DIR__, "..", "deps")) +const version = "{version}" + +{component_includes} + +function __init__() + Dash.register_package( + Dash.ResourcePkg( + "{project_shortname}", + resources_path, + version = version, + [ + {resources_dist} + ] + ) + ) +end +end +''' + +jl_projecttoml_string = ''' +name = "{package_name}" +uuid = "{package_uuid}" +{authors} +version = "{version}" + +[deps] +Dash = "{dash_uuid}" + +[compact] +julia = "1.1" +Dash = ">=0.1" +''' + +jl_component_include_string = 'include("{name}.jl")' + +jl_resource_tuple_string = '''Dash.Resource( + relative_package_path = {relative_package_path}, + external_url = {external_url}, + dynamic = {dynamic}, + async = {async_string}, + type = :{type} +)''' + +def jl_package_name(namestring): + s = namestring.split("_") + return "".join(w.capitalize() for w in s) def stringify_wildcards(wclist, no_symbol=False): if no_symbol: @@ -306,6 +364,69 @@ def format_fn_name(prefix, name): return name.lower() +def generate_metadata_strings(resources, type): + def noting_or_string(v): + return '"{}"'.format(v) if v else "nothing" + return [jl_resource_tuple_string.format( + relative_package_path = noting_or_string(resource.get("relative_package_path", "")), + external_url = noting_or_string(resource.get("external_url", "")), + dynamic = str(resource.get("dynamic", 'nothing')).lower(), + type = type, + async_string = ":{}".format(str(resource.get("async")).lower()) + if "async" in resource.keys() + else 'nothing' + ) for resource in resources] + + +def generate_package_file(project_shortname, components, pkg_data, prefix): + package_name = jl_package_name(project_shortname) + + sys.path.insert(0, os.getcwd()) + mod = importlib.import_module(project_shortname) + js_dist = getattr(mod, "_js_dist", []) + css_dist = getattr(mod, "_css_dist", []) + project_ver = pkg_data.get("version") + + resources_dist = ",\n".join( + generate_metadata_strings(js_dist, "js") + generate_metadata_strings(css_dist, "css") + ) + + package_string = jl_package_file_string.format( + package_name = package_name, + component_includes = "\n".join( + [jl_component_include_string.format(name = format_fn_name(prefix, comp_name)) for comp_name in components] + ), + resources_dist = resources_dist, + version = project_ver, + project_shortname = project_shortname + + ) + file_path = os.path.join("src", package_name + ".jl") + with open(file_path, "w") as f: + f.write(package_string) + print("Generated {}".format(file_path)) + +def generate_toml_file(project_shortname, pkg_data): + package_author = pkg_data.get("author", "") + project_ver = pkg_data.get("version") + package_name = jl_package_name(project_shortname) + u = uuid.UUID(jl_dash_uuid) + package_uuid = uuid.UUID(hex=u.hex[:-12] + hex(hash(package_name))[-12:]) + + authors_string = 'authors = ["{}"]'.format(package_author) if package_author else "" + + toml_string = jl_projecttoml_string.format( + package_name = package_name, + package_uuid = package_uuid, + version = project_ver, + authors = authors_string, + dash_uuid = jl_dash_uuid + ) + file_path = "Project.toml" + with open(file_path, "w") as f: + f.write(toml_string) + print("Generated {}".format(file_path)) + def generate_class_string(name, props, description, project_shortname, prefix): # Ensure props are ordered with children first filtered_props = reorder_props(filter_props(props)) @@ -374,6 +495,7 @@ def generate_module( project_shortname, components, metadata, + pkg_data, prefix, **kwargs ): @@ -398,3 +520,9 @@ def generate_module( for sourcemap in glob.glob("{}/*.map".format(project_shortname)): shutil.copy(sourcemap, "deps/") + + generate_package_file(project_shortname, components, pkg_data, prefix) + generate_toml_file(project_shortname, pkg_data) + + + diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 6ddc64db05..9512b8864a 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -111,6 +111,10 @@ def generate_components( ) if jlprefix is not False: + if pkg_data is None: + with open("package.json", "r") as f: + pkg_data = safe_json_loads(f.read()) + generator_methods.append( functools.partial(generate_struct_file, prefix=jlprefix) ) @@ -140,6 +144,7 @@ def generate_components( project_shortname, components, metadata, + pkg_data, jlprefix ) From 98af3362bccc79a97e26dca8f264498d36bcb6f6 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 00:52:54 -0400 Subject: [PATCH 06/18] :necktie: linter fixes --- dash/development/_jl_components_generation.py | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index dec24323bf..3cb6eda3f3 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -13,7 +13,7 @@ from ._all_keywords import julia_keywords from ._py_components_generation import reorder_props -#uuid of Dash Julia package. Used as base for component package uuid +# uuid of Dash Julia package. Used as base for component package uuid jl_dash_uuid = "1b08a953-4be3-4667-9a23-3db579824955" # Declaring longer string templates as globals to improve @@ -105,10 +105,12 @@ type = :{type} )''' + def jl_package_name(namestring): s = namestring.split("_") return "".join(w.capitalize() for w in s) + def stringify_wildcards(wclist, no_symbol=False): if no_symbol: wcstring = "|".join( @@ -243,13 +245,7 @@ def get_jl_type(type_object): """ js_type_name = type_object["name"] js_to_jl_types = get_jl_prop_types(type_object=type_object) - if ( - "computed" in type_object - and type_object["computed"] - or type_object.get("type", "") == "function" - ): - return "" - elif js_type_name in js_to_jl_types: + if js_type_name in js_to_jl_types: prop_type = js_to_jl_types[js_type_name]() return prop_type return "" @@ -364,27 +360,27 @@ def format_fn_name(prefix, name): return name.lower() -def generate_metadata_strings(resources, type): +def generate_metadata_strings(resources, metatype): def noting_or_string(v): return '"{}"'.format(v) if v else "nothing" return [jl_resource_tuple_string.format( - relative_package_path = noting_or_string(resource.get("relative_package_path", "")), - external_url = noting_or_string(resource.get("external_url", "")), - dynamic = str(resource.get("dynamic", 'nothing')).lower(), - type = type, - async_string = ":{}".format(str(resource.get("async")).lower()) - if "async" in resource.keys() - else 'nothing' + relative_package_path=noting_or_string(resource.get("relative_package_path", "")), + external_url=noting_or_string(resource.get("external_url", "")), + dynamic=str(resource.get("dynamic", 'nothing')).lower(), + type=metatype, + async_string=":{}".format(str(resource.get("async")).lower()) + if "async" in resource.keys() + else 'nothing' ) for resource in resources] def generate_package_file(project_shortname, components, pkg_data, prefix): package_name = jl_package_name(project_shortname) - + sys.path.insert(0, os.getcwd()) mod = importlib.import_module(project_shortname) - js_dist = getattr(mod, "_js_dist", []) - css_dist = getattr(mod, "_css_dist", []) + js_dist = getattr(mod, "_js_dist", []) + css_dist = getattr(mod, "_css_dist", []) project_ver = pkg_data.get("version") resources_dist = ",\n".join( @@ -392,13 +388,13 @@ def generate_package_file(project_shortname, components, pkg_data, prefix): ) package_string = jl_package_file_string.format( - package_name = package_name, - component_includes = "\n".join( - [jl_component_include_string.format(name = format_fn_name(prefix, comp_name)) for comp_name in components] + package_name=package_name, + component_includes="\n".join( + [jl_component_include_string.format(name=format_fn_name(prefix, comp_name)) for comp_name in components] ), - resources_dist = resources_dist, - version = project_ver, - project_shortname = project_shortname + resources_dist=resources_dist, + version=project_ver, + project_shortname=project_shortname ) file_path = os.path.join("src", package_name + ".jl") @@ -406,6 +402,7 @@ def generate_package_file(project_shortname, components, pkg_data, prefix): f.write(package_string) print("Generated {}".format(file_path)) + def generate_toml_file(project_shortname, pkg_data): package_author = pkg_data.get("author", "") project_ver = pkg_data.get("version") @@ -416,17 +413,18 @@ def generate_toml_file(project_shortname, pkg_data): authors_string = 'authors = ["{}"]'.format(package_author) if package_author else "" toml_string = jl_projecttoml_string.format( - package_name = package_name, - package_uuid = package_uuid, - version = project_ver, - authors = authors_string, - dash_uuid = jl_dash_uuid + package_name=package_name, + package_uuid=package_uuid, + version=project_ver, + authors=authors_string, + dash_uuid=jl_dash_uuid ) file_path = "Project.toml" with open(file_path, "w") as f: f.write(toml_string) print("Generated {}".format(file_path)) + def generate_class_string(name, props, description, project_shortname, prefix): # Ensure props are ordered with children first filtered_props = reorder_props(filter_props(props)) @@ -523,6 +521,3 @@ def generate_module( generate_package_file(project_shortname, components, pkg_data, prefix) generate_toml_file(project_shortname, pkg_data) - - - From ddaa3a39f2ed50111a4fe57dabe63a00a6cfc6f1 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 01:19:42 -0400 Subject: [PATCH 07/18] :necktie: more linter fixes --- dash/development/component_generator.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index 9512b8864a..d7628d7fd2 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -48,7 +48,7 @@ def generate_components( rdepends="", rimports="", rsuggests="", - jlprefix=None, + jlprefix=None ): project_shortname = project_shortname.replace("-", "_").rstrip("/\\") @@ -94,6 +94,10 @@ def generate_components( generator_methods = [generate_class_file] + if rprefix is not None or jlprefix is not None: + with open("package.json", "r") as f: + pkg_data = safe_json_loads(f.read()) + if rprefix is not None: if not os.path.exists("man"): os.makedirs("man") @@ -104,17 +108,11 @@ def generate_components( rpkg_data = yaml.safe_load(yamldata) else: rpkg_data = None - with open("package.json", "r") as f: - pkg_data = safe_json_loads(f.read()) generator_methods.append( functools.partial(write_class_file, prefix=rprefix, rpkg_data=rpkg_data) ) if jlprefix is not False: - if pkg_data is None: - with open("package.json", "r") as f: - pkg_data = safe_json_loads(f.read()) - generator_methods.append( functools.partial(generate_struct_file, prefix=jlprefix) ) @@ -228,9 +226,9 @@ def byteify(input_object): return OrderedDict( [(byteify(key), byteify(value)) for key, value in input_object.iteritems()] ) - elif isinstance(input_object, list): + if isinstance(input_object, list): return [byteify(element) for element in input_object] - elif isinstance(input_object, unicode): # noqa:F821 + if isinstance(input_object, unicode): # noqa:F821 return input_object.encode("utf-8") return input_object From 429e86ffca22d8a35881f0d9a172efaa900a6e70 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 01:36:21 -0400 Subject: [PATCH 08/18] :see_no_evil: replace False with None --- 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 d7628d7fd2..51a839c388 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -112,7 +112,7 @@ def generate_components( functools.partial(write_class_file, prefix=rprefix, rpkg_data=rpkg_data) ) - if jlprefix is not False: + if jlprefix is not None: generator_methods.append( functools.partial(generate_struct_file, prefix=jlprefix) ) @@ -137,7 +137,7 @@ def generate_components( rsuggests, ) - if jlprefix is not False: + if jlprefix is not None: generate_module( project_shortname, components, From 1a15907e4370b27c4f250741b32401ef36a5e5e8 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 21:56:50 -0400 Subject: [PATCH 09/18] :pencil2: s/noting_or_string/nothing_or_string/g --- dash/development/_jl_components_generation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 3cb6eda3f3..11040c5144 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -361,11 +361,11 @@ def format_fn_name(prefix, name): def generate_metadata_strings(resources, metatype): - def noting_or_string(v): + def nothing_or_string(v): return '"{}"'.format(v) if v else "nothing" return [jl_resource_tuple_string.format( - relative_package_path=noting_or_string(resource.get("relative_package_path", "")), - external_url=noting_or_string(resource.get("external_url", "")), + relative_package_path=nothing_or_string(resource.get("relative_package_path", "")), + external_url=nothing_or_string(resource.get("external_url", "")), dynamic=str(resource.get("dynamic", 'nothing')).lower(), type=metatype, async_string=":{}".format(str(resource.get("async")).lower()) From 5b2edcdccb30133fc0cdbd588811428648b4d576 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 21:57:52 -0400 Subject: [PATCH 10/18] :hocho: disable too many args --- dash/development/_jl_components_generation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 11040c5144..b8741c89c1 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -296,8 +296,6 @@ def create_docstring_jl(component_name, props, description): ) -# pylint: disable=too-many-arguments -# pylint: disable=too-many-arguments def create_prop_docstring_jl( prop_name, type_object, From fd24e50822975e725e1b5431cafed4f101ac738e Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 22:03:42 -0400 Subject: [PATCH 11/18] use Real --- dash/development/_jl_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index b8741c89c1..1c252b77f0 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -155,7 +155,7 @@ def shape_or_exact(): return dict( array=lambda: "Array", bool=lambda: "Bool", - number=lambda: "Float64", + number=lambda: "Real", string=lambda: "String", object=lambda: "Dict", any=lambda: "Bool | Float64 | String | Dict | Array", From 2e813bf1feafed958eeccbae74949980ee469ae5 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 18 May 2020 22:22:36 -0400 Subject: [PATCH 12/18] move newline to authors string --- dash/development/_jl_components_generation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 1c252b77f0..c5ebba5169 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -84,8 +84,7 @@ jl_projecttoml_string = ''' name = "{package_name}" uuid = "{package_uuid}" -{authors} -version = "{version}" +{authors}version = "{version}" [deps] Dash = "{dash_uuid}" @@ -408,7 +407,7 @@ def generate_toml_file(project_shortname, pkg_data): u = uuid.UUID(jl_dash_uuid) package_uuid = uuid.UUID(hex=u.hex[:-12] + hex(hash(package_name))[-12:]) - authors_string = 'authors = ["{}"]'.format(package_author) if package_author else "" + authors_string = 'authors = ["{}"]\n'.format(package_author) if package_author else "" toml_string = jl_projecttoml_string.format( package_name=package_name, From d5a5a1c6b479de0e6821c5f53a27da0b729eea55 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 19 May 2020 12:57:10 -0400 Subject: [PATCH 13/18] :necktie: try 20 lines similarity threshold --- .pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index f81e253526..cb11ae92f9 100644 --- a/.pylintrc +++ b/.pylintrc @@ -270,7 +270,7 @@ ignore-docstrings=yes ignore-imports=no # Minimum lines number of a similarity. -min-similarity-lines=10 +min-similarity-lines=20 [SPELLING] @@ -466,4 +466,4 @@ known-third-party=enchant # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception \ No newline at end of file +overgeneral-exceptions=Exception From 7eb50221615f89f4fad48a5bb4402c395d793ebb Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Thu, 21 May 2020 13:26:47 -0400 Subject: [PATCH 14/18] s/Float64/Real --- dash/development/_jl_components_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index c5ebba5169..11bc940c6f 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -157,7 +157,7 @@ def shape_or_exact(): number=lambda: "Real", string=lambda: "String", object=lambda: "Dict", - any=lambda: "Bool | Float64 | String | Dict | Array", + any=lambda: "Bool | Real | String | Dict | Array", element=lambda: "dash component", node=lambda: "a list of or a singular dash component, string or number", # React's PropTypes.oneOf From ac65fe940153d850f7410a380bf2af765a8a57ff Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Fri, 12 Jun 2020 20:45:58 +0300 Subject: [PATCH 15/18] reverse Dash dependency --- dash/development/_jl_components_generation.py | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 1cbf19d63b..baa86f3942 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -13,6 +13,9 @@ from ._all_keywords import julia_keywords from ._py_components_generation import reorder_props +# uuid of DashBase Julia package. +jl_dash_base_uuid = "03207cf0-e2b3-4b91-9ca8-690cf0fb507e" + # uuid of Dash Julia package. Used as base for component package uuid jl_dash_uuid = "1b08a953-4be3-4667-9a23-3db579824955" @@ -23,43 +26,31 @@ export {funcname} """ - {funcname}(;kwargs...) - {funcname}(children::Any;kwargs...) - {funcname}(children_maker::Function;kwargs...) + {funcname}(;kwargs...){childrens_signatures} {docstring} """ function {funcname}(; kwargs...) - available_props = Set(Symbol[{component_props}]) - wild_props = Set(Symbol[{wildcard_symbols}]) - wild_regs = r"^(?{wildcard_names})" - - result = Component("{element_name}", "{module_name}", Dict{{Symbol, Any}}(), available_props, Set(Symbol[{wildcard_symbols}])) - - for (prop, value) = pairs(kwargs) - m = match(wild_regs, string(prop)) - if (length(wild_props) == 0 || isnothing(m)) && !(prop in available_props) - throw(ArgumentError("Invalid property $(string(prop)) for component " * "{funcname}")) - end - - push!(result.props, prop => Front.to_dash(value)) - end - - return result + available_props = Symbol[{component_props}] + wild_props = Symbol[{wildcard_symbols}] + return Component("{funcname}", "{element_name}", "{module_name}", available_props, wild_props; kwargs...) end +{childrens_definitions} +''' # noqa:E501 -function {funcname}(children::Any; kwargs...) - result = {funcname}(;kwargs...) - push!(result.props, :children => Front.to_dash(children)) - return result -end +jl_childrens_signatures = ''' + {funcname}(children::Any;kwargs...) + {funcname}(children_maker::Function;kwargs...) +''' +jl_childrens_definitions = ''' +{funcname}(children::Any; kwargs...) = {funcname}(;kwargs..., children = children) {funcname}(children_maker::Function; kwargs...) = {funcname}(children_maker(); kwargs...) -''' # noqa:E501 +''' jl_package_file_string = ''' module {package_name} -using Dash +using {base_package} const resources_path = realpath(joinpath( @__DIR__, "..", "deps")) const version = "{version}" @@ -67,8 +58,8 @@ {component_includes} function __init__() - Dash.register_package( - Dash.ResourcePkg( + DashBase.register_package( + DashBase.ResourcePkg( "{project_shortname}", resources_path, version = version, @@ -76,6 +67,7 @@ {resources_dist} ] ) + ) end end @@ -87,16 +79,16 @@ {authors}version = "{version}" [deps] -Dash = "{dash_uuid}" +{base_package} = "{dash_uuid}" [compact] -julia = "1.1" -Dash = ">=0.1" +julia = "1.2" +{base_package} = ">=0.1" ''' jl_component_include_string = 'include("{name}.jl")' -jl_resource_tuple_string = '''Dash.Resource( +jl_resource_tuple_string = '''DashBase.Resource( relative_package_path = {relative_package_path}, external_url = {external_url}, dynamic = {dynamic}, @@ -104,6 +96,7 @@ type = :{type} )''' +core_packages = ['dash_html_components', 'dash_core_components', 'dash_table'] def jl_package_name(namestring): s = namestring.split("_") @@ -370,6 +363,14 @@ def nothing_or_string(v): else 'nothing' ) for resource in resources] +def is_core_package(project_shortname): + return project_shortname in core_packages + +def base_package_name(project_shortname): + return "DashBase" if is_core_package(project_shortname) else "Dash" + +def base_package_uid(project_shortname): + return jl_dash_base_uuid if is_core_package(project_shortname) else jl_base_uuid def generate_package_file(project_shortname, components, pkg_data, prefix): package_name = jl_package_name(project_shortname) @@ -391,7 +392,8 @@ def generate_package_file(project_shortname, components, pkg_data, prefix): ), resources_dist=resources_dist, version=project_ver, - project_shortname=project_shortname + project_shortname=project_shortname, + base_package=base_package_name(project_shortname) ) file_path = os.path.join("src", package_name + ".jl") @@ -413,7 +415,8 @@ def generate_toml_file(project_shortname, pkg_data): package_uuid=package_uuid, version=project_ver, authors=authors_string, - dash_uuid=jl_dash_uuid + base_package=base_package_name(project_shortname), + dash_uuid=base_package_uid(project_shortname), ) file_path = "Project.toml" with open(file_path, "w") as f: @@ -428,7 +431,7 @@ def generate_class_string(name, props, description, project_shortname, prefix): docstring = create_docstring_jl( component_name=name, props=filtered_props, description=description - ).replace("\r\n", "\n") + ).replace("\r\n", "\n").replace('$', '\$') wclist = get_wildcards_jl(props) default_paramtext = "" @@ -451,6 +454,10 @@ def generate_class_string(name, props, description, project_shortname, prefix): for p in prop_keys ) + has_children = "children" in prop_keys + funcname = format_fn_name(prefix, name) + childrens_signatures = jl_childrens_signatures.format(funcname=funcname) if has_children else "" + childrens_definitions = jl_childrens_definitions.format(funcname=funcname) if has_children else "" return jl_component_string.format( funcname=format_fn_name(prefix, name), docstring=docstring, @@ -459,6 +466,8 @@ def generate_class_string(name, props, description, project_shortname, prefix): wildcard_names=stringify_wildcards(wclist, no_symbol=True), element_name=name, module_name=project_shortname, + childrens_signatures = childrens_signatures, + childrens_definitions = childrens_definitions ) From 650b9d3fc06008c1e595a7f83cce002ea4c976c8 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Tue, 16 Jun 2020 17:04:39 +0300 Subject: [PATCH 16/18] naming fix --- dash/development/_jl_components_generation.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index baa86f3942..6d82aced73 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -26,7 +26,7 @@ export {funcname} """ - {funcname}(;kwargs...){childrens_signatures} + {funcname}(;kwargs...){children_signatures} {docstring} """ @@ -35,15 +35,15 @@ wild_props = Symbol[{wildcard_symbols}] return Component("{funcname}", "{element_name}", "{module_name}", available_props, wild_props; kwargs...) end -{childrens_definitions} +{children_definitions} ''' # noqa:E501 -jl_childrens_signatures = ''' +jl_children_signatures = ''' {funcname}(children::Any;kwargs...) {funcname}(children_maker::Function;kwargs...) ''' -jl_childrens_definitions = ''' +jl_children_definitions = ''' {funcname}(children::Any; kwargs...) = {funcname}(;kwargs..., children = children) {funcname}(children_maker::Function; kwargs...) = {funcname}(children_maker(); kwargs...) ''' @@ -456,8 +456,8 @@ def generate_class_string(name, props, description, project_shortname, prefix): has_children = "children" in prop_keys funcname = format_fn_name(prefix, name) - childrens_signatures = jl_childrens_signatures.format(funcname=funcname) if has_children else "" - childrens_definitions = jl_childrens_definitions.format(funcname=funcname) if has_children else "" + children_signatures = jl_children_signatures.format(funcname=funcname) if has_children else "" + children_definitions = jl_children_definitions.format(funcname=funcname) if has_children else "" return jl_component_string.format( funcname=format_fn_name(prefix, name), docstring=docstring, @@ -466,8 +466,8 @@ def generate_class_string(name, props, description, project_shortname, prefix): wildcard_names=stringify_wildcards(wclist, no_symbol=True), element_name=name, module_name=project_shortname, - childrens_signatures = childrens_signatures, - childrens_definitions = childrens_definitions + children_signatures = children_signatures, + children_definitions = children_definitions ) From 3e4916526f2fdcd08def90497ab0c4b7b278cdb9 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 9 Jul 2020 08:16:33 -0400 Subject: [PATCH 17/18] lint _jl_components_generation --- dash/development/_jl_components_generation.py | 134 ++++++++++-------- 1 file changed, 71 insertions(+), 63 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 6d82aced73..2e6ada2000 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -38,17 +38,17 @@ {children_definitions} ''' # noqa:E501 -jl_children_signatures = ''' +jl_children_signatures = """ {funcname}(children::Any;kwargs...) {funcname}(children_maker::Function;kwargs...) -''' +""" -jl_children_definitions = ''' +jl_children_definitions = """ {funcname}(children::Any; kwargs...) = {funcname}(;kwargs..., children = children) {funcname}(children_maker::Function; kwargs...) = {funcname}(children_maker(); kwargs...) -''' +""" -jl_package_file_string = ''' +jl_package_file_string = """ module {package_name} using {base_package} @@ -67,13 +67,13 @@ {resources_dist} ] ) - + ) end end -''' +""" -jl_projecttoml_string = ''' +jl_projecttoml_string = """ name = "{package_name}" uuid = "{package_uuid}" {authors}version = "{version}" @@ -84,19 +84,20 @@ [compact] julia = "1.2" {base_package} = ">=0.1" -''' +""" jl_component_include_string = 'include("{name}.jl")' -jl_resource_tuple_string = '''DashBase.Resource( +jl_resource_tuple_string = """DashBase.Resource( relative_package_path = {relative_package_path}, external_url = {external_url}, dynamic = {dynamic}, async = {async_string}, type = :{type} -)''' +)""" + +core_packages = ["dash_html_components", "dash_core_components", "dash_table"] -core_packages = ['dash_html_components', 'dash_core_components', 'dash_table'] def jl_package_name(namestring): s = namestring.split("_") @@ -105,13 +106,9 @@ def jl_package_name(namestring): def stringify_wildcards(wclist, no_symbol=False): if no_symbol: - wcstring = "|".join( - '{}-'.format(item) for item in wclist - ) + wcstring = "|".join("{}-".format(item) for item in wclist) else: - wcstring = ", ".join( - 'Symbol("{}-")'.format(item) for item in wclist - ) + wcstring = ", ".join('Symbol("{}-")'.format(item) for item in wclist) return wcstring @@ -289,11 +286,7 @@ def create_docstring_jl(component_name, props, description): def create_prop_docstring_jl( - prop_name, - type_object, - required, - description, - indent_num, + prop_name, type_object, required, description, indent_num, ): """ Create the Dash component prop docstring @@ -353,24 +346,34 @@ def format_fn_name(prefix, name): def generate_metadata_strings(resources, metatype): def nothing_or_string(v): return '"{}"'.format(v) if v else "nothing" - return [jl_resource_tuple_string.format( - relative_package_path=nothing_or_string(resource.get("relative_package_path", "")), - external_url=nothing_or_string(resource.get("external_url", "")), - dynamic=str(resource.get("dynamic", 'nothing')).lower(), - type=metatype, - async_string=":{}".format(str(resource.get("async")).lower()) - if "async" in resource.keys() - else 'nothing' - ) for resource in resources] + + return [ + jl_resource_tuple_string.format( + relative_package_path=nothing_or_string( + resource.get("relative_package_path", "") + ), + external_url=nothing_or_string(resource.get("external_url", "")), + dynamic=str(resource.get("dynamic", "nothing")).lower(), + type=metatype, + async_string=":{}".format(str(resource.get("async")).lower()) + if "async" in resource.keys() + else "nothing", + ) + for resource in resources + ] + def is_core_package(project_shortname): return project_shortname in core_packages + def base_package_name(project_shortname): return "DashBase" if is_core_package(project_shortname) else "Dash" + def base_package_uid(project_shortname): - return jl_dash_base_uuid if is_core_package(project_shortname) else jl_base_uuid + return jl_dash_base_uuid if is_core_package(project_shortname) else jl_dash_uuid + def generate_package_file(project_shortname, components, pkg_data, prefix): package_name = jl_package_name(project_shortname) @@ -382,25 +385,31 @@ def generate_package_file(project_shortname, components, pkg_data, prefix): project_ver = pkg_data.get("version") resources_dist = ",\n".join( - generate_metadata_strings(js_dist, "js") + generate_metadata_strings(css_dist, "css") + generate_metadata_strings(js_dist, "js") + + generate_metadata_strings(css_dist, "css") ) package_string = jl_package_file_string.format( package_name=package_name, component_includes="\n".join( - [jl_component_include_string.format(name=format_fn_name(prefix, comp_name)) for comp_name in components] + [ + jl_component_include_string.format( + name=format_fn_name(prefix, comp_name) + ) + for comp_name in components + ] ), resources_dist=resources_dist, version=project_ver, project_shortname=project_shortname, - base_package=base_package_name(project_shortname) - + base_package=base_package_name(project_shortname), ) file_path = os.path.join("src", package_name + ".jl") with open(file_path, "w") as f: f.write(package_string) print("Generated {}".format(file_path)) + def generate_toml_file(project_shortname, pkg_data): package_author = pkg_data.get("author", "") project_ver = pkg_data.get("version") @@ -408,7 +417,9 @@ def generate_toml_file(project_shortname, pkg_data): u = uuid.UUID(jl_dash_uuid) package_uuid = uuid.UUID(hex=u.hex[:-12] + hex(hash(package_name))[-12:]) - authors_string = 'authors = ["{}"]\n'.format(package_author) if package_author else "" + authors_string = ( + 'authors = ["{}"]\n'.format(package_author) if package_author else "" + ) toml_string = jl_projecttoml_string.format( package_name=package_name, @@ -423,15 +434,20 @@ def generate_toml_file(project_shortname, pkg_data): f.write(toml_string) print("Generated {}".format(file_path)) + def generate_class_string(name, props, description, project_shortname, prefix): # Ensure props are ordered with children first filtered_props = reorder_props(filter_props(props)) prop_keys = list(filtered_props.keys()) - docstring = create_docstring_jl( - component_name=name, props=filtered_props, description=description - ).replace("\r\n", "\n").replace('$', '\$') + docstring = ( + create_docstring_jl( + component_name=name, props=filtered_props, description=description + ) + .replace("\r\n", "\n") + .replace("$", "\\$") + ) wclist = get_wildcards_jl(props) default_paramtext = "" @@ -449,15 +465,16 @@ def generate_class_string(name, props, description, project_shortname, prefix): ).format(item, name) ) - default_paramtext += ", ".join( - ":{}".format(p) - for p in prop_keys - ) + default_paramtext += ", ".join(":{}".format(p) for p in prop_keys) has_children = "children" in prop_keys funcname = format_fn_name(prefix, name) - children_signatures = jl_children_signatures.format(funcname=funcname) if has_children else "" - children_definitions = jl_children_definitions.format(funcname=funcname) if has_children else "" + children_signatures = ( + jl_children_signatures.format(funcname=funcname) if has_children else "" + ) + children_definitions = ( + jl_children_definitions.format(funcname=funcname) if has_children else "" + ) return jl_component_string.format( funcname=format_fn_name(prefix, name), docstring=docstring, @@ -466,21 +483,17 @@ def generate_class_string(name, props, description, project_shortname, prefix): wildcard_names=stringify_wildcards(wclist, no_symbol=True), element_name=name, module_name=project_shortname, - children_signatures = children_signatures, - children_definitions = children_definitions + children_signatures=children_signatures, + children_definitions=children_definitions, ) -def generate_struct_file( - name, props, description, project_shortname, prefix -): +def generate_struct_file(name, props, description, project_shortname, prefix): props = reorder_props(props=props) import_string = "# AUTO GENERATED FILE - DO NOT EDIT\n" - class_string = generate_class_string(name, - props, - description, - project_shortname, - prefix) + class_string = generate_class_string( + name, props, description, project_shortname, prefix + ) file_name = format_fn_name(prefix, name) + ".jl" @@ -494,12 +507,7 @@ def generate_struct_file( # pylint: disable=unused-argument def generate_module( - project_shortname, - components, - metadata, - pkg_data, - prefix, - **kwargs + project_shortname, components, metadata, pkg_data, prefix, **kwargs ): # the Julia source directory for the package won't exist on first call # create the Julia directory if it is missing From 439c8aaf3ad410c94915d03b8fdbba147c20cd3e Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 9 Jul 2020 17:12:48 -0400 Subject: [PATCH 18/18] minor component generator cleanup --- dash/development/_jl_components_generation.py | 20 ++++++------------- dash/development/_py_components_generation.py | 8 +++----- dash/development/_r_components_generation.py | 4 ++-- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/dash/development/_jl_components_generation.py b/dash/development/_jl_components_generation.py index 2e6ada2000..7b16deef20 100644 --- a/dash/development/_jl_components_generation.py +++ b/dash/development/_jl_components_generation.py @@ -113,12 +113,7 @@ def stringify_wildcards(wclist, no_symbol=False): def get_wildcards_jl(props): - prop_keys = list(props.keys()) - wildcards = [] - for key in prop_keys: - if key.endswith("-*"): - wildcards.append(key.replace("-*", "")) - return wildcards + return [key.replace("-*", "") for key in props if key.endswith("-*")] def get_jl_prop_types(type_object): @@ -126,7 +121,7 @@ def get_jl_prop_types(type_object): def shape_or_exact(): return "lists containing elements {}.\n{}".format( - ", ".join("'{}'".format(t) for t in list(type_object["value"].keys())), + ", ".join("'{}'".format(t) for t in type_object["value"]), "Those elements have the following types:\n{}".format( "\n".join( create_prop_docstring_jl( @@ -136,7 +131,7 @@ def shape_or_exact(): description=prop.get("description", ""), indent_num=1, ) - for prop_name, prop in list(type_object["value"].items()) + for prop_name, prop in type_object["value"].items() ) ), ) @@ -265,11 +260,8 @@ def create_docstring_jl(component_name, props, description): # Ensure props are ordered with children first props = reorder_props(props=props) - return ( - """A{n} {name} component.\n{description} -Keyword arguments:\n{args}""" - ).format( - n="n" if component_name[0].lower() in ["a", "e", "i", "o", "u"] else "", + return ("A{n} {name} component.\n{description}\nKeyword arguments:\n{args}").format( + n="n" if component_name[0].lower() in "aeiou" else "", name=component_name, description=description, args="\n".join( @@ -280,7 +272,7 @@ def create_docstring_jl(component_name, props, description): description=prop["description"], indent_num=0, ) - for p, prop in list(filter_props(props).items()) + for p, prop in filter_props(props).items() ), ) diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index c6aa769eaf..24928d1fcb 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -225,11 +225,9 @@ def create_docstring(component_name, props, description): props = reorder_props(props=props) return ( - """A{n} {name} component.\n{description} - -Keyword arguments:\n{args}""" + "A{n} {name} component.\n{description}\n\nKeyword arguments:\n{args}" ).format( - n="n" if component_name[0].lower() in ["a", "e", "i", "o", "u"] else "", + n="n" if component_name[0].lower() in "aeiou" else "", name=component_name, description=description, args="\n".join( @@ -242,7 +240,7 @@ def create_docstring(component_name, props, description): indent_num=0, is_flow_type="flowType" in prop and "type" not in prop, ) - for p, prop in list(filter_props(props).items()) + for p, prop in filter_props(props).items() ), ) diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index e1e4344d36..e06600ee85 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -846,7 +846,7 @@ def get_r_prop_types(type_object): def shape_or_exact(): return "lists containing elements {}.\n{}".format( - ", ".join("'{}'".format(t) for t in list(type_object["value"].keys())), + ", ".join("'{}'".format(t) for t in type_object["value"]), "Those elements have the following types:\n{}".format( "\n".join( create_prop_docstring_r( @@ -856,7 +856,7 @@ def shape_or_exact(): description=prop.get("description", ""), indent_num=1, ) - for prop_name, prop in list(type_object["value"].items()) + for prop_name, prop in type_object["value"].items() ) ), )