From 4e4e0b3c316028bd7135d03921deb0bcc9db1fea Mon Sep 17 00:00:00 2001 From: Steven Clontz Date: Mon, 17 Jun 2024 15:13:13 -0700 Subject: [PATCH] add support for `deploy` attribute on `` (#743) * add support for deploy attribute * improved `to_deploy` logic * test deploy/deploy-dir logic * revert elaborate example, create project to test deploy stuff programmatically --- pretext/project/__init__.py | 34 +++++++++++++++-------- tests/test_project.py | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/pretext/project/__init__.py b/pretext/project/__init__.py index 88fd010e..eb914442 100644 --- a/pretext/project/__init__.py +++ b/pretext/project/__init__.py @@ -113,9 +113,22 @@ class Target(pxml.BaseXmlModel, tag="target", search_mode=SearchMode.UNORDERED): name="braille-mode", default=BrailleMode.EMBOSS ) stringparams: t.Dict[str, str] = pxml.element(default={}) - # A path to the subdirectory of your deployment where this target will live. + + # Specify whether this target should be included in a deployment + deploy: t.Optional[str] = pxml.attr(name="deploy", default=None) + # A non-default path to the subdirectory of your deployment where this target will live. deploy_dir: t.Optional[Path] = pxml.attr(name="deploy-dir", default=None) + # Case-check different combos of `deploy` / `deploy-dir` + def to_deploy(self) -> bool: + deploy = self.deploy + if deploy is None: + # didn't specify `deploy`, so deploy iff there's a `deploy_dir` + return self.deploy_dir is not None + else: + # specified `deploy` attr, so deploy iff choice isn't "no" + return deploy.lower() != "no" + # These attributes have complex validators. # Note that in each case, since we may not have validated the properties we refer to in values, we should use `values.get` instead of `values[]`. # @@ -329,14 +342,14 @@ def output_dir_abspath(self) -> Path: def output_dir_relpath(self) -> Path: return self._project.output_dir / self.output_dir - def deploy_dir_abspath(self) -> t.Optional[Path]: + def deploy_dir_abspath(self) -> Path: if self.deploy_dir is None: - return None + return self._project.stage_abspath() / self.name return self._project.stage_abspath() / self.deploy_dir - def deploy_dir_relpath(self) -> t.Optional[Path]: + def deploy_dir_relpath(self) -> Path: if self.deploy_dir is None: - return None + return self._project.stage / self.name return self._project.stage / self.deploy_dir def xsl_abspath(self) -> t.Optional[Path]: @@ -1045,6 +1058,7 @@ def server_validator(cls, v: t.List[Server]) -> t.List[Server]: return v # This validator sets the `_path`, which is provided in the validation context. It can't be loaded from the XML, since this is metadata about the XML (the location of the file it was loaded from). + # (It's unclear why the typing of the next line is causing issues.) @model_validator(mode="after") def set_metadata(self, info: ValidationInfo) -> "Project": c = info.context @@ -1287,7 +1301,7 @@ def init_core(self) -> None: core.set_executables(exec_dict) def deploy_targets(self) -> t.List[Target]: - return [target for target in self.targets if target.deploy_dir is not None] + return [tgt for tgt in self.targets if tgt.to_deploy()] def stage_deployment(self) -> None: # First empty the stage directory (as long as it is safely in the project directory). @@ -1301,8 +1315,6 @@ def stage_deployment(self) -> None: self.stage_abspath().mkdir(parents=True, exist_ok=True) # Stage all configured targets for deployment for target in self.deploy_targets(): - deploy_dir = target.deploy_dir - assert deploy_dir is not None if not target.output_dir_abspath().exists(): log.warning( f"No build for `{target.name}` was found in the directory `{target.output_dir_abspath()}`." @@ -1312,12 +1324,10 @@ def stage_deployment(self) -> None: else: shutil.copytree( target.output_dir_abspath(), - (self.stage_abspath() / deploy_dir).resolve(), + target.deploy_dir_abspath(), dirs_exist_ok=True, ) - log.info( - f"Staging `{target.name}` at `{self.stage_abspath() / deploy_dir}`." - ) + log.info(f"Staging `{target.name}` at `{target.deploy_dir_abspath()}`.") # If no target is configured to deploy, stage the default target if len(self.deploy_targets()) == 0: target = self.get_target() diff --git a/tests/test_project.py b/tests/test_project.py index 99345a99..d4f54c9e 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -364,12 +364,67 @@ def test_asset_table(tmp_path: Path) -> None: def test_deploy(tmp_path: Path) -> None: + # check permutations of deploy / deploy-dir + project = pr.Project(ptx_version="2") + + t = project.new_target( + name="none-deploy-none-dir", + format="html", + ) + assert not t.to_deploy() + assert t.deploy_dir_relpath().name == t.name + + t = project.new_target( + name="no-deploy-none-dir", + format="html", + deploy="no", + ) + assert not t.to_deploy() + assert t.deploy_dir_relpath().name == t.name + + t = project.new_target( + name="yes-deploy-none-dir", + format="html", + deploy="yes", + ) + assert t.to_deploy() + assert t.deploy_dir_relpath().name == t.name + + t = project.new_target( + name="none-deploy-custom-dir", + format="html", + deploy_dir="custom", + ) + assert t.to_deploy() + assert t.deploy_dir_relpath().name == "custom" + + t = project.new_target( + name="no-deploy-custom-dir", + format="html", + deploy="no", + deploy_dir="custom", + ) + assert not t.to_deploy() + assert t.deploy_dir_relpath().name == "custom" + + t = project.new_target( + name="yes-deploy-custom-dir", + format="html", + deploy="yes", + deploy_dir="custom", + ) + assert t.to_deploy() + assert t.deploy_dir_relpath().name == "custom" + + # check elaborate settings prj_path = tmp_path / "elaborate" shutil.copytree( EXAMPLES_DIR / "projects" / "project_refactor" / "elaborate", prj_path ) with utils.working_directory(prj_path): project = pr.Project.parse() + assert project.get_target("web").to_deploy() + assert not project.get_target("print").to_deploy() project.get_target("web").build() assert (prj_path / "build" / "here" / "web" / "index.html").exists() project.deploy(stage_only=True)