From e45735f2ee7e0484e9804c30d8a6c3cf6408b00c Mon Sep 17 00:00:00 2001 From: Tyler Mathis <35553152+tsmathis@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:42:58 -0800 Subject: [PATCH] start cli --- mpcontribs-lux/mpcontribs/lux/cli/__init__.py | 0 .../mpcontribs/lux/cli/display/__init__.py | 0 .../mpcontribs/lux/cli/display/tree.py | 67 +++++++++++++++++ .../mpcontribs/lux/cli/entry_point.py | 12 ++++ .../mpcontribs/lux/cli/project/__init__.py | 10 +++ .../mpcontribs/lux/cli/project/scaffold.py | 72 +++++++++++++++++++ .../mpcontribs/lux/cli/project/utils.py | 50 +++++++++++++ .../mpcontribs/lux/cli/schema/__init__.py | 11 +++ .../mpcontribs/lux/cli/schema/autogen.py | 7 ++ mpcontribs-lux/pyproject.toml | 7 ++ 10 files changed, 236 insertions(+) create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/__init__.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/display/__init__.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/display/tree.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/entry_point.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/project/__init__.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/project/scaffold.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/project/utils.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/schema/__init__.py create mode 100644 mpcontribs-lux/mpcontribs/lux/cli/schema/autogen.py diff --git a/mpcontribs-lux/mpcontribs/lux/cli/__init__.py b/mpcontribs-lux/mpcontribs/lux/cli/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mpcontribs-lux/mpcontribs/lux/cli/display/__init__.py b/mpcontribs-lux/mpcontribs/lux/cli/display/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mpcontribs-lux/mpcontribs/lux/cli/display/tree.py b/mpcontribs-lux/mpcontribs/lux/cli/display/tree.py new file mode 100644 index 000000000..da31a8b09 --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/display/tree.py @@ -0,0 +1,67 @@ +import pathlib +import tempfile + +from rich import print +from rich.markup import escape +from rich.text import Text +from rich.tree import Tree + +from mpcontribs.lux.cli.project.utils import build_scaffold + + +# github.com/textualize/rich/blob/master/examples/tree.py +def walk_directory(directory: pathlib.Path, tree: Tree) -> None: + """Recursively build a Tree with directory contents.""" + paths = sorted( + pathlib.Path(directory).iterdir(), + key=lambda path: (path.is_file(), path.name.lower()), + ) + for path in paths: + if path.name.startswith("."): + continue + if path.is_dir(): + style = "dim" if path.name.startswith("__") else "" + branch = tree.add( + f"[bold bright_blue] [link file://{path}]{escape(path.name)}", + style=style, + guide_style=style, + ) + walk_directory(path, branch) + else: + text_filename = Text(path.name, "white") + text_filename.stylize(f"link file://{path}") + tree.add(text_filename) + + +def visualize_scaffold( + user_space: str, + projects: set[str], + structure: str, + include_analysis: bool, + include_pipeline: bool, + include_readme: bool, + extra_reqs: bool, +): + tree = Tree( + f"[bold bright_blue] [link file://{user_space}]{user_space}", + guide_style="white", + ) + + user_space_root = pathlib.Path(__file__).parent.parent.parent.joinpath( + "projects", user_space + ) + + with tempfile.TemporaryDirectory(prefix=str(user_space_root)) as tmpdir: + build_scaffold( + pathlib.Path(tmpdir), + user_space, + projects, + structure, + include_analysis, + include_pipeline, + include_readme, + extra_reqs, + ) + + walk_directory(pathlib.Path(tmpdir), tree) + print(tree) diff --git a/mpcontribs-lux/mpcontribs/lux/cli/entry_point.py b/mpcontribs-lux/mpcontribs/lux/cli/entry_point.py new file mode 100644 index 000000000..004e1cfcf --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/entry_point.py @@ -0,0 +1,12 @@ +import click + +from mpcontribs.lux.cli.project import project +from mpcontribs.lux.cli.schema import schema + + +@click.group() +def lux(): ... + + +lux.add_command(project) +lux.add_command(schema) diff --git a/mpcontribs-lux/mpcontribs/lux/cli/project/__init__.py b/mpcontribs-lux/mpcontribs/lux/cli/project/__init__.py new file mode 100644 index 000000000..fd064fa28 --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/project/__init__.py @@ -0,0 +1,10 @@ +import click + +from mpcontribs.lux.cli.project.scaffold import scaffold + + +@click.group() +def project(): ... + + +project.add_command(scaffold) diff --git a/mpcontribs-lux/mpcontribs/lux/cli/project/scaffold.py b/mpcontribs-lux/mpcontribs/lux/cli/project/scaffold.py new file mode 100644 index 000000000..69f8e62f9 --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/project/scaffold.py @@ -0,0 +1,72 @@ +import pathlib + +import click + +from mpcontribs.lux.cli.display.tree import visualize_scaffold +from mpcontribs.lux.cli.project.utils import build_scaffold + + +@click.command() +@click.option("--user-space", default=None) +@click.option("--projects", multiple=True, default=None) +@click.option("--structure", type=click.Choice(["directory", "module"]), default=None) +@click.option("--include-analysis", is_flag=True, default=True) +@click.option("--include-pipeline", is_flag=True, default=True) +@click.option("--include-readme", is_flag=True, default=True) +@click.option("--extra-reqs", is_flag=True, default=True) +def scaffold( + user_space, + projects, + structure, + include_analysis, + include_pipeline, + include_readme, + extra_reqs, +): + if any([not x for x in (user_space, structure, projects)]): + user_space = click.prompt("Name space for user project") + structure = click.prompt("Structure of user project [directory, module]") + projects = click.prompt( + "Project names to scaffold in user project name space" + ).split(" ") + include_analysis = click.confirm("Include analysis module?") + include_pipeline = click.confirm("Include pipeline module?") + include_readme = click.confirm("Include project README.md?") + extra_reqs = click.confirm( + "Include file for extra python requirements/libraries?" + ) + + projects = set(projects) + + click.echo( + "The following project scaffold will be created in 'mpcontribs-lux.mpcontribs.projects':" + ) + visualize_scaffold( + user_space, + projects, + structure, + include_analysis, + include_pipeline, + include_readme, + extra_reqs, + ) + if click.confirm("Proceed? y/N", abort=True): + user_space_root = pathlib.Path(__file__).parent.parent.parent.joinpath( + "projects", user_space + ) + + user_space_root.mkdir() + + build_scaffold( + user_space_root, + user_space, + projects, + structure, + include_analysis, + include_pipeline, + include_readme, + extra_reqs, + ) + click.echo( + f"Project scaffold created at 'mpcontribs-lux.mpcontribs.projects.{user_space}'!" + ) diff --git a/mpcontribs-lux/mpcontribs/lux/cli/project/utils.py b/mpcontribs-lux/mpcontribs/lux/cli/project/utils.py new file mode 100644 index 000000000..48c52335d --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/project/utils.py @@ -0,0 +1,50 @@ +import pathlib + + +def build_scaffold( + root, + user_space, + projects, + structure, + include_analysis, + include_pipeline, + include_readme, + extra_reqs, +): + for proj in projects: + proj_dir = root / proj + proj_dir.mkdir() + pathlib.Path(proj_dir, "__init__.py").touch() + + if include_readme: + pathlib.Path(proj_dir, "README.md").touch() + if extra_reqs: + pathlib.Path(proj_dir, "pip-extra-requirements.txt").touch() + + match structure: + case "directory": + schema_dir = pathlib.Path(proj_dir) / "schemas" + schema_dir.mkdir() + pathlib.Path(schema_dir, "__init__.py").touch() + pathlib.Path(schema_dir, "schema_1.py").touch() + + if include_analysis: + analysis_dir = pathlib.Path(proj_dir) / "analysis" + analysis_dir.mkdir() + pathlib.Path(analysis_dir, "__init__.py").touch() + pathlib.Path(analysis_dir, "analysis_1.py").touch() + + if include_pipeline: + pipeline_dir = pathlib.Path(proj_dir) / "pipelines" + pipeline_dir.mkdir() + pathlib.Path(pipeline_dir, "__init__.py").touch() + pathlib.Path(pipeline_dir, "pipeline_1.py").touch() + + case "module": + pathlib.Path(proj_dir, "schema_1.py").touch() + + if include_analysis: + pathlib.Path(proj_dir, "analysis_1.py").touch() + + if include_pipeline: + pathlib.Path(proj_dir, "pipeline_1.py").touch() diff --git a/mpcontribs-lux/mpcontribs/lux/cli/schema/__init__.py b/mpcontribs-lux/mpcontribs/lux/cli/schema/__init__.py new file mode 100644 index 000000000..4182b82b7 --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/schema/__init__.py @@ -0,0 +1,11 @@ +import click + +from mpcontribs.lux.cli.schema.autogen import autogen + + +@click.group() +def schema(): + pass + + +schema.add_command(autogen) diff --git a/mpcontribs-lux/mpcontribs/lux/cli/schema/autogen.py b/mpcontribs-lux/mpcontribs/lux/cli/schema/autogen.py new file mode 100644 index 000000000..2c5b83721 --- /dev/null +++ b/mpcontribs-lux/mpcontribs/lux/cli/schema/autogen.py @@ -0,0 +1,7 @@ +import click + + +@click.command() +def autogen(): + # TODO: SchemaGenerator(file).pydantic_schema from --file, write output to --output-file (?) + ... diff --git a/mpcontribs-lux/pyproject.toml b/mpcontribs-lux/pyproject.toml index c259b97ee..63d9ea08e 100644 --- a/mpcontribs-lux/pyproject.toml +++ b/mpcontribs-lux/pyproject.toml @@ -35,7 +35,14 @@ authors = [ Homepage = "https://github.com/materialsproject/MPContribs" Documentation = "https://docs.materialsproject.org/services/mpcontribs" +[project.scripts] +lux = "mpcontribs.lux.cli.entry_point:lux" + [project.optional-dependencies] +cli = [ + "click", + "rich", +] test = [ "pre-commit", "pytest",