done
This commit is contained in:
@ -0,0 +1,296 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
import shlex
|
||||
import os
|
||||
import argparse
|
||||
import shutil
|
||||
import functools
|
||||
import pkg_resources
|
||||
import yaml
|
||||
|
||||
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
|
||||
from ._jl_components_generation import generate_struct_file
|
||||
from ._jl_components_generation import generate_module
|
||||
from ._generate_prop_types import generate_prop_types
|
||||
|
||||
reserved_words = [
|
||||
"UNDEFINED",
|
||||
"REQUIRED",
|
||||
"to_plotly_json",
|
||||
"available_properties",
|
||||
"available_wildcard_properties",
|
||||
"_.*",
|
||||
]
|
||||
|
||||
|
||||
class _CombinedFormatter(
|
||||
argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals, too-many-arguments, too-many-branches, too-many-statements
|
||||
def generate_components(
|
||||
components_source,
|
||||
project_shortname,
|
||||
package_info_filename="package.json",
|
||||
ignore="^_",
|
||||
rprefix=None,
|
||||
rdepends="",
|
||||
rimports="",
|
||||
rsuggests="",
|
||||
jlprefix=None,
|
||||
metadata=None,
|
||||
keep_prop_order=None,
|
||||
max_props=None,
|
||||
custom_typing_module=None,
|
||||
):
|
||||
|
||||
project_shortname = project_shortname.replace("-", "_").rstrip("/\\")
|
||||
|
||||
is_windows = sys.platform == "win32"
|
||||
|
||||
extract_path = pkg_resources.resource_filename("dash", "extract-meta.js")
|
||||
|
||||
reserved_patterns = "|".join(f"^{p}$" for p in reserved_words)
|
||||
|
||||
os.environ["NODE_PATH"] = "node_modules"
|
||||
|
||||
shutil.copyfile(
|
||||
"package.json", os.path.join(project_shortname, package_info_filename)
|
||||
)
|
||||
|
||||
if not metadata:
|
||||
env = os.environ.copy()
|
||||
|
||||
# Ensure local node modules is used when the script is packaged.
|
||||
env["MODULES_PATH"] = os.path.abspath("./node_modules")
|
||||
|
||||
cmd = shlex.split(
|
||||
f'node {extract_path} "{ignore}" "{reserved_patterns}" {components_source}',
|
||||
posix=not is_windows,
|
||||
)
|
||||
|
||||
proc = subprocess.Popen( # pylint: disable=consider-using-with
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=is_windows,
|
||||
env=env,
|
||||
)
|
||||
out, err = proc.communicate()
|
||||
status = proc.poll()
|
||||
|
||||
if err:
|
||||
print(err.decode(), file=sys.stderr)
|
||||
|
||||
if not out:
|
||||
print(
|
||||
f"Error generating metadata in {project_shortname} (status={status})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
metadata = safe_json_loads(out.decode("utf-8"))
|
||||
|
||||
py_generator_kwargs = {
|
||||
"custom_typing_module": custom_typing_module,
|
||||
}
|
||||
if keep_prop_order is not None:
|
||||
keep_prop_order = [
|
||||
component.strip(" ") for component in keep_prop_order.split(",")
|
||||
]
|
||||
py_generator_kwargs["prop_reorder_exceptions"] = keep_prop_order
|
||||
|
||||
if max_props:
|
||||
py_generator_kwargs["max_props"] = max_props
|
||||
|
||||
generator_methods = [functools.partial(generate_class_file, **py_generator_kwargs)]
|
||||
|
||||
pkg_data = None
|
||||
if rprefix is not None or jlprefix is not None:
|
||||
with open("package.json", "r", encoding="utf-8") as f:
|
||||
pkg_data = safe_json_loads(f.read())
|
||||
|
||||
rpkg_data = None
|
||||
if rprefix is not None:
|
||||
if not os.path.exists("man"):
|
||||
os.makedirs("man")
|
||||
if not os.path.exists("R"):
|
||||
os.makedirs("R")
|
||||
if os.path.isfile("dash-info.yaml"):
|
||||
with open("dash-info.yaml", encoding="utf-8") as yamldata:
|
||||
rpkg_data = yaml.safe_load(yamldata)
|
||||
generator_methods.append(
|
||||
functools.partial(write_class_file, prefix=rprefix, rpkg_data=rpkg_data)
|
||||
)
|
||||
|
||||
if jlprefix is not None:
|
||||
generator_methods.append(
|
||||
functools.partial(generate_struct_file, prefix=jlprefix)
|
||||
)
|
||||
|
||||
components = generate_classes_files(project_shortname, metadata, *generator_methods)
|
||||
|
||||
generate_prop_types(
|
||||
metadata,
|
||||
project_shortname,
|
||||
custom_typing_module=custom_typing_module,
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.join(project_shortname, "metadata.json"), "w", encoding="utf-8"
|
||||
) as f:
|
||||
json.dump(metadata, f, separators=(",", ":"))
|
||||
|
||||
generate_imports(project_shortname, components)
|
||||
|
||||
if rprefix is not None:
|
||||
generate_exports(
|
||||
project_shortname,
|
||||
components,
|
||||
metadata,
|
||||
pkg_data,
|
||||
rpkg_data,
|
||||
rprefix,
|
||||
rdepends,
|
||||
rimports,
|
||||
rsuggests,
|
||||
)
|
||||
|
||||
if jlprefix is not None:
|
||||
generate_module(project_shortname, components, metadata, pkg_data, jlprefix)
|
||||
|
||||
|
||||
def safe_json_loads(s):
|
||||
jsondata_unicode = json.loads(s, object_pairs_hook=OrderedDict)
|
||||
if sys.version_info[0] >= 3:
|
||||
return jsondata_unicode
|
||||
return byteify(jsondata_unicode)
|
||||
|
||||
|
||||
def component_build_arg_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="dash-generate-components",
|
||||
formatter_class=_CombinedFormatter,
|
||||
description="Generate dash components by extracting the metadata "
|
||||
"using react-docgen. Then map the metadata to Python classes.",
|
||||
)
|
||||
parser.add_argument("components_source", help="React components source directory.")
|
||||
parser.add_argument(
|
||||
"project_shortname", help="Name of the project to export the classes files."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--package-info-filename",
|
||||
default="package.json",
|
||||
help="The filename of the copied `package.json` to `project_shortname`",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--ignore",
|
||||
default="^_",
|
||||
help="Files/directories matching the pattern will be ignored",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--r-prefix",
|
||||
help="Specify a prefix for Dash for R component names, write "
|
||||
"components to R dir, create R package.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--r-depends",
|
||||
default="",
|
||||
help="Specify a comma-separated list of R packages to be "
|
||||
"inserted into the Depends field of the DESCRIPTION file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--r-imports",
|
||||
default="",
|
||||
help="Specify a comma-separated list of R packages to be "
|
||||
"inserted into the Imports field of the DESCRIPTION file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--r-suggests",
|
||||
default="",
|
||||
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.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--keep-prop-order",
|
||||
default=None,
|
||||
help="Specify a comma-separated list of components which will use the prop "
|
||||
"order described in the component proptypes instead of alphabetically reordered "
|
||||
"props. Pass the 'ALL' keyword to have every component retain "
|
||||
"its original prop order.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-props",
|
||||
type=int,
|
||||
default=250,
|
||||
help="Specify the max number of props to list in the component signature. "
|
||||
"More props will still be shown in the docstring, and will still work when "
|
||||
"provided as kwargs to the component. Python <3.7 only supports 255 args, "
|
||||
"but you may also want to reduce further for improved readability at the "
|
||||
"expense of auto-completion for the later props. Use 0 to include all props.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--custom-typing-module",
|
||||
type=str,
|
||||
default="dash_prop_typing",
|
||||
help=" Module containing custom typing definition for components."
|
||||
"Can contains two variables:\n"
|
||||
" - custom_imports: dict[ComponentName, list[str]].\n"
|
||||
" - custom_props: dict[ComponentName, dict[PropName, function]].\n",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def cli():
|
||||
# Add current path for loading modules.
|
||||
sys.path.insert(0, ".")
|
||||
args = component_build_arg_parser().parse_args()
|
||||
generate_components(
|
||||
args.components_source,
|
||||
args.project_shortname,
|
||||
package_info_filename=args.package_info_filename,
|
||||
ignore=args.ignore,
|
||||
rprefix=args.r_prefix,
|
||||
rdepends=args.r_depends,
|
||||
rimports=args.r_imports,
|
||||
rsuggests=args.r_suggests,
|
||||
jlprefix=args.jl_prefix,
|
||||
keep_prop_order=args.keep_prop_order,
|
||||
max_props=args.max_props,
|
||||
custom_typing_module=args.custom_typing_module,
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=undefined-variable
|
||||
def byteify(input_object):
|
||||
if isinstance(input_object, dict):
|
||||
return OrderedDict(
|
||||
[(byteify(key), byteify(value)) for key, value in input_object.items()]
|
||||
)
|
||||
if isinstance(input_object, list):
|
||||
return [byteify(element) for element in input_object]
|
||||
if isinstance(input_object, str): # noqa:F821
|
||||
return input_object.encode(encoding="utf-8")
|
||||
return input_object
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
Reference in New Issue
Block a user