From b56cc1048cbc6b28101cb6e250b57e4355d4b6ff Mon Sep 17 00:00:00 2001 From: Daniel Elero Date: Thu, 9 Jan 2020 00:49:55 +0100 Subject: [PATCH 1/3] Initial work on project scaffolding --- textX-LS/core/setup.py | 7 ++- textX-LS/core/textx_ls_core/exceptions.py | 27 ++++++---- .../generators/scaffolding/__init__.py | 52 +++++++++++++++++++ .../project_name/__init__.py.template | 9 ++++ .../project_name/grammar.tx.template | 3 ++ .../textx-language/setup.py.template | 36 +++++++++++++ textX-LS/server/textx_ls_server/server.py | 22 +++++++- 7 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py create mode 100644 textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/__init__.py.template create mode 100644 textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/grammar.tx.template create mode 100644 textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/setup.py.template diff --git a/textX-LS/core/setup.py b/textX-LS/core/setup.py index fecb4ac..8c7e9a2 100644 --- a/textX-LS/core/setup.py +++ b/textX-LS/core/setup.py @@ -50,7 +50,12 @@ packages=packages, include_package_data=True, package_data={"": ["*.tx"]}, - install_requires=["textX>=2.1.0", "click==7.0", "wheel_inspect==1.3.0"], + install_requires=[ + "textX>=2.1.0", + "click==7.0", + "jinja2>=2", + "wheel_inspect==1.3.0", + ], extras_require={ "dev": dev_require, "test": tests_require, diff --git a/textX-LS/core/textx_ls_core/exceptions.py b/textX-LS/core/textx_ls_core/exceptions.py index 9964db8..0d72c8d 100644 --- a/textX-LS/core/textx_ls_core/exceptions.py +++ b/textX-LS/core/textx_ls_core/exceptions.py @@ -42,15 +42,6 @@ def __init__(self, project_name, dist_location, detailed_err_msg): self.detailed_err_msg = detailed_err_msg -class UninstallTextXProjectError(TextXLSError): - """Indicates an error while uninstalling a textX project.""" - - def __init__(self, project_name, detailed_err_msg): - super().__init__("Failed to uninstall project: {}".format(project_name)) - self.project_name = project_name - self.detailed_err_msg = detailed_err_msg - - class LanguageNotRegistered(TextXLSError): """Indicates an error if language can't be parsed with any of registered metamodels. """ @@ -71,3 +62,21 @@ def __init__(self, file_name): file_name ) ) + + +class ScaffoldTextXProjectError(TextXLSError): + """Indicates an error while scaffolding a textX project.""" + + def __init__(self, project_name, detailed_err_msg): + super().__init__("Failed to scaffold project: {}".format(project_name)) + self.project_name = project_name + self.detailed_err_msg = detailed_err_msg + + +class UninstallTextXProjectError(TextXLSError): + """Indicates an error while uninstalling a textX project.""" + + def __init__(self, project_name, detailed_err_msg): + super().__init__("Failed to uninstall project: {}".format(project_name)) + self.project_name = project_name + self.detailed_err_msg = detailed_err_msg diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py b/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py new file mode 100644 index 0000000..982090b --- /dev/null +++ b/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py @@ -0,0 +1,52 @@ +import shutil +from functools import partial +from os import rename +from os.path import dirname, join, relpath + +import jinja2 + +from ...exceptions import ScaffoldTextXProjectError + +templates_path = join(dirname(__file__), "templates") +textx_language_template_path = join(templates_path, "textx-language") + + +jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(textx_language_template_path), + autoescape=True, + lstrip_blocks=True, + trim_blocks=True, +) + + +def _populate_copy(project_name: str, src: str, dest: str) -> str: + """Populates jinja template.""" + if src.endswith("template"): + template_rel_path = relpath(src, textx_language_template_path) + template = jinja_env.get_template(template_rel_path) + dest = dest.replace(".template", "") + with open(dest, "w") as f: + f.write(template.render(project_name=project_name)) + return dest + else: + return shutil.copy2(src, dest) + + +def scaffold_project(project_name: str, dest_dir: str) -> str: + """Creates a new textX language project in a given destination directory.""" + project_name = project_name.lower() + dest = join(dest_dir, project_name) + + try: + # Copy populated project template to dest path + shutil.copytree( + textx_language_template_path, + dest, + copy_function=partial(_populate_copy, project_name), + ) + # Rename project name + rename(join(dest, "project_name"), join(dest, "tx_{}".format(project_name))) + + return dest + except (shutil.Error, OSError, IOError) as e: + raise ScaffoldTextXProjectError(project_name, str(e)) from e diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/__init__.py.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/__init__.py.template new file mode 100644 index 0000000..1d7721f --- /dev/null +++ b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/__init__.py.template @@ -0,0 +1,9 @@ +from os.path import dirname, join + +from textx import language, metamodel_from_file + + +@language("{{project_name}}", "*.{{project_name}}") +def {{project_name}}(): + "Language project scaffolded with textX extension." + return metamodel_from_file(join(dirname(__file__), "grammar.tx")) diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/grammar.tx.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/grammar.tx.template new file mode 100644 index 0000000..e2e9cbc --- /dev/null +++ b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/grammar.tx.template @@ -0,0 +1,3 @@ +{{project_name|upper}}: + "{{project_name}}" +; diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/setup.py.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/setup.py.template new file mode 100644 index 0000000..f0b400a --- /dev/null +++ b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/setup.py.template @@ -0,0 +1,36 @@ +import codecs +import os + +from setuptools import find_packages, setup + +PACKAGE_NAME = "tx-{{project_name}}" +VERSION = "0.1.0" +AUTHOR = "textX" +DESCRIPTION = "textX extension for language {{project_name}}" +KEYWORDS = "textX DSL python domain specific languages workflow" + +setup( + name=PACKAGE_NAME, + version=VERSION, + description=DESCRIPTION, + author=AUTHOR, + keywords=KEYWORDS, + packages=find_packages(), + include_package_data=True, + package_data={"": ["*.tx"]}, + install_requires=["textx_ls_core"], + entry_points={"textx_languages": ["{{project_name}} = tx_{{project_name}}:{{project_name}}"]}, + classifiers=[ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + ], +) diff --git a/textX-LS/server/textx_ls_server/server.py b/textX-LS/server/textx_ls_server/server.py index 2b34b42..91ac81c 100644 --- a/textX-LS/server/textx_ls_server/server.py +++ b/textX-LS/server/textx_ls_server/server.py @@ -21,6 +21,7 @@ GenerateExtensionError, GenerateSyntaxHighlightError, InstallTextXProjectError, + ScaffoldTextXProjectError, UninstallTextXProjectError, ) from textx_ls_core.features.generators import ( @@ -33,6 +34,7 @@ install_project_async, uninstall_project_async, ) +from textx_ls_core.generators.scaffolding import scaffold_project from textx_ls_core.models import TextXProject from textx_ls_core.utils import compare_project_names @@ -194,8 +196,24 @@ def cmd_project_list(ls: TextXLanguageServer, params) -> List[TextXProject]: @textx_server.command(TextXLanguageServer.CMD_PROJECT_SCAFFOLD) -def cmd_project_scaffold(ls: TextXLanguageServer, params) -> None: - ls.show_message("Not implemented") +def cmd_scaffold_project(ls: TextXLanguageServer, params) -> None: + """Command that scaffolds textX project. + + Args: + params: project name + Returns: + None + Raises: + None + + """ + project_name = params[0] + try: + dest = scaffold_project(project_name, ls.workspace.root_path) + ls.show_message("Project successfully scaffolded at {}.".format(dest)) + except ScaffoldTextXProjectError as e: + ls.show_errors(str(e)) + return {} @textx_server.command(TextXLanguageServer.CMD_PROJECT_UNINSTALL) From 95e034a79a14c35d1389bfc894d3b6e2e1613788 Mon Sep 17 00:00:00 2001 From: Daniel Elero Date: Thu, 9 Jan 2020 01:33:43 +0100 Subject: [PATCH 2/3] Update docstrings --- .../generators/scaffolding/__init__.py | 39 ++++++++++++++++--- .../__init__.py.template | 0 .../grammar.tx.template | 0 textX-LS/server/textx_ls_server/server.py | 7 ++-- 4 files changed, 37 insertions(+), 9 deletions(-) rename textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/{project_name => tx_package_name}/__init__.py.template (100%) rename textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/{project_name => tx_package_name}/grammar.tx.template (100%) diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py b/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py index 982090b..162abba 100644 --- a/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py +++ b/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py @@ -1,7 +1,7 @@ import shutil from functools import partial from os import rename -from os.path import dirname, join, relpath +from os.path import dirname, exists, join, relpath import jinja2 @@ -20,7 +20,18 @@ def _populate_copy(project_name: str, src: str, dest: str) -> str: - """Populates jinja template.""" + """Populates jinja template. + + Args: + project_name: project name + src: source directory path + dest: destination directory path + Returns: + Destination directory path where template is coppied to + Raises: + None + + """ if src.endswith("template"): template_rel_path = relpath(src, textx_language_template_path) template = jinja_env.get_template(template_rel_path) @@ -32,11 +43,28 @@ def _populate_copy(project_name: str, src: str, dest: str) -> str: return shutil.copy2(src, dest) -def scaffold_project(project_name: str, dest_dir: str) -> str: - """Creates a new textX language project in a given destination directory.""" +def scaffold_language_project(project_name: str, dest_dir: str) -> str: + """Creates a new textX language project in a given destination directory. + + Args: + project_name: project name + dest_dir: destination directory path where language project is scaffolded + Returns: + same as `dest_dir` + Raises: + ScaffoldTextXProjectError: If any of `shutil.Error`, `OSError`, `IOError` error + types occurs + + """ project_name = project_name.lower() dest = join(dest_dir, project_name) + if exists(dest): + raise ScaffoldTextXProjectError( + project_name, + "Project with name {} already exist at {}".format(project_name, dest), + ) + try: # Copy populated project template to dest path shutil.copytree( @@ -45,8 +73,9 @@ def scaffold_project(project_name: str, dest_dir: str) -> str: copy_function=partial(_populate_copy, project_name), ) # Rename project name - rename(join(dest, "project_name"), join(dest, "tx_{}".format(project_name))) + rename(join(dest, "tx_package_name"), join(dest, "tx_{}".format(project_name))) return dest except (shutil.Error, OSError, IOError) as e: + shutil.rmtree(dest) raise ScaffoldTextXProjectError(project_name, str(e)) from e diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/__init__.py.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_name/__init__.py.template similarity index 100% rename from textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/__init__.py.template rename to textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_name/__init__.py.template diff --git a/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/grammar.tx.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_name/grammar.tx.template similarity index 100% rename from textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/project_name/grammar.tx.template rename to textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_name/grammar.tx.template diff --git a/textX-LS/server/textx_ls_server/server.py b/textX-LS/server/textx_ls_server/server.py index 91ac81c..873f0d3 100644 --- a/textX-LS/server/textx_ls_server/server.py +++ b/textX-LS/server/textx_ls_server/server.py @@ -34,7 +34,7 @@ install_project_async, uninstall_project_async, ) -from textx_ls_core.generators.scaffolding import scaffold_project +from textx_ls_core.generators.scaffolding import scaffold_language_project from textx_ls_core.models import TextXProject from textx_ls_core.utils import compare_project_names @@ -209,11 +209,10 @@ def cmd_scaffold_project(ls: TextXLanguageServer, params) -> None: """ project_name = params[0] try: - dest = scaffold_project(project_name, ls.workspace.root_path) - ls.show_message("Project successfully scaffolded at {}.".format(dest)) + dest = scaffold_language_project(project_name, ls.workspace.root_path) + ls.show_message("Project successfully scaffolded at {}.".format(str(dest))) except ScaffoldTextXProjectError as e: ls.show_errors(str(e)) - return {} @textx_server.command(TextXLanguageServer.CMD_PROJECT_UNINSTALL) From d9013970d0905e86efa9854403b8a126d1d02205 Mon Sep 17 00:00:00 2001 From: Daniel Elero Date: Mon, 13 Jan 2020 01:20:15 +0100 Subject: [PATCH 3/3] Add project name validation --- client/src/services/projectService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/services/projectService.ts b/client/src/services/projectService.ts index b132711..6a0d411 100644 --- a/client/src/services/projectService.ts +++ b/client/src/services/projectService.ts @@ -125,10 +125,10 @@ export class ProjectService implements IProjectService { ignoreFocusOut: true, placeHolder: "Enter a project name.", validateInput: (value: string) => { - if (value && value.trim().length > 0) { + if (value && value.trim().length > 0 && /^[\w\.-]+$/.test(value)) { return null; } else { - return "Project name is required."; + return "Project name is required and has to be consisted of letters, numbers, ., -, _ ."; } }, });