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, ., -, _ ."; } }, }); 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..162abba --- /dev/null +++ b/textX-LS/core/textx_ls_core/generators/scaffolding/__init__.py @@ -0,0 +1,81 @@ +import shutil +from functools import partial +from os import rename +from os.path import dirname, exists, 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. + + 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) + 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_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( + textx_language_template_path, + dest, + copy_function=partial(_populate_copy, project_name), + ) + # Rename 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/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/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_name/__init__.py.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_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/tx_package_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/tx_package_name/grammar.tx.template b/textX-LS/core/textx_ls_core/generators/scaffolding/templates/textx-language/tx_package_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/tx_package_name/grammar.tx.template @@ -0,0 +1,3 @@ +{{project_name|upper}}: + "{{project_name}}" +; diff --git a/textX-LS/server/textx_ls_server/server.py b/textX-LS/server/textx_ls_server/server.py index 2b34b42..873f0d3 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_language_project from textx_ls_core.models import TextXProject from textx_ls_core.utils import compare_project_names @@ -194,8 +196,23 @@ 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_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)) @textx_server.command(TextXLanguageServer.CMD_PROJECT_UNINSTALL)