Skip to content

Commit

Permalink
Merge pull request #8 from mosa11aei/feat/pre-post-hooks
Browse files Browse the repository at this point in the history
feat: Create config hooks
  • Loading branch information
mosa11aei authored Jul 18, 2024
2 parents bba72b5 + af97060 commit f3350f1
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 8 deletions.
56 changes: 50 additions & 6 deletions docs/ConfigObjects.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,8 @@ The user can then complete this file, and run `fppm config --apply` on the packa
Metavars are bits of metadata that you can ship in your config object. These are predominantly only good for `fppm`, but there may be a time where your end user may want to edit these. Metavars transfer over to the config object's fillable, and are written with similar annotation syntax as the config object description:

- `@! output = <path>` is the path where the generated config file from the fillable is moved to.
- `@! pre_hook = <path/to/pythonScript.py>` is the path to your prehook script.
- `@! post_hook = <path/to/pythonScript.py>` is the path to your posthook script.

> [!IMPORTANT]
> Note that currently, pre and post hooks are not yet implemented, as designs for them and security considerations are being considered.
- `@! pre_hook = <path/to/pythonScript.py>` is the path to your prehook script (.py, relative to package root).
- `@! post_hook = <path/to/pythonScript.py>` is the path to your posthook script (.py, relative to package root).

Let's take the above `TopologyDefs.hpp` example and add the `output` metavar to it:

Expand Down Expand Up @@ -111,4 +108,51 @@ __output: myCustomFolder
# === END METADATA ===
```

Note that files that do not have cookiecutter variables *can still* have metavars.
Note that files that do not have cookiecutter variables *can still* have metavars.

### Config Hooks

As you may notice, there are metavars for "pre-" and "post-hooks" for your config object. Config hooks are Python files that contain functions that may be useful in the fillables phase (pre-hook) or in the generated config file (post-hook) phase. For example, you may want to verify that certain variables in a fillable follow a certain standard (pre-hook), or you may want to verify the contents of the output file (post-hook).

In the background, fppm runs:

```bash
# pre-hook
<path-to-python> <path-to-your-hook-script.py> -f <abs/path/to/fillable.yaml>
# post-hook
<path-to-python> <path-to-your-hook-script.py> -g <abs/path/to/generated-config-file>
```

Config hooks always execute when the user runs `fppm config --apply <yourPackage>`. In the pre-hook, your script is passed the user-completed YAML fillable file. In the post-hook, your script is passed the cookiecutter-generated config file.

For your script to be compatible with the command line style of passing the files, start with the following template in your Python file:

```py
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-f",
"--fillable",
type=str,
required=False,
)
parser.add_argument(
"-g",
"--generated",
type=str,
required=False,
)
args = parser.parse_args()
if args.fillable:
# == YOUR FUNCTION HERE ==
pass
if args.generated:
# == YOUR FUNCTION HERE ==
pass
```
31 changes: 29 additions & 2 deletions src/fppm/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from cookiecutter.exceptions import OutputDirExistsException
from cookiecutter.main import cookiecutter
import fppm.cli.utils as FppmUtils
import fppm.cli.config_hooks as ConfigHooks


def pull_cookiecutter_variables(configObject, packagePath):
Expand Down Expand Up @@ -340,6 +341,19 @@ def apply_config_fillables(args, context):
else:
metaVarContent[key] = fillableContent[key]

if "__pre_hook" in metaVarContent.keys():
totalPath = str(Path(f"_fprime_packages/{packageFolder}/{metaVarContent['__pre_hook']}").absolute())

if os.path.exists(totalPath) == False:
FppmUtils.print_error(f"[ERR]: Pre-hook script [{metaVarContent['__pre_hook']}] not found.")
else:
pre_hook_output = ConfigHooks.pre_hook(totalPath, str(Path(fillable).absolute()))

if pre_hook_output == 1:
return 1

FppmUtils.print_warning(f"[INFO]: Pre-hook printed: {pre_hook_output}")


cookiecutter_name = "{{ cookiecutter.temporary___ }}"

Expand Down Expand Up @@ -379,8 +393,8 @@ def apply_config_fillables(args, context):

for file in os.listdir(actual_cookiecutter):
if ".fpp" in file:
print(
f"\n[!!!] Output file {file} is an FPP file; remember to add it as a source in CMakeLists.txt. \n"
FppmUtils.print_warning(
f"[!!!] Output file {file} is an FPP file; remember to add it as a source in CMakeLists.txt."
)

with open(f"{actual_cookiecutter}/{file}", "r") as fileContent:
Expand Down Expand Up @@ -418,6 +432,19 @@ def apply_config_fillables(args, context):

with open(f"{actual_cookiecutter}/{file}", "w") as fileContent:
fileContent.write(content)

if "__post_hook" in metaVarContent.keys():
totalPath = str(Path(f"_fprime_packages/{packageFolder}/{metaVarContent['__post_hook']}").absolute())

if os.path.exists(totalPath) == False:
FppmUtils.print_error(f"[ERR]: Post-hook script [{metaVarContent['__post_hook']}] not found.")
else:
post_hook_output = ConfigHooks.post_hook(totalPath, str(Path(f"{actual_cookiecutter}/{file}").absolute()))

if post_hook_output == 1:
return 1

FppmUtils.print_warning(f"[INFO]: Post-hook printed: {post_hook_output}")

if "__output" in metaVarContent.keys():
if os.path.exists(f"{metaVarContent['__output']}/{file}"):
Expand Down
44 changes: 44 additions & 0 deletions src/fppm/cli/config_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import fppm.cli.utils as FppmUtils
import os
import subprocess

def getPython():
try:
output = subprocess.run(["which", "python"], capture_output=True, check=True)
if output.returncode == 0:
return output.stdout.decode("utf-8").strip()
except subprocess.CalledProcessError as e:
try:
output = subprocess.run(["which", "python3"], capture_output=True, check=True)
if output.returncode == 0:
return output.stdout.decode("utf-8").strip()
except subprocess.CalledProcessError as e:
FppmUtils.print_error(f"[ERR]: Unable to find a Python interpreter. Ensure python or python3 is added to your path.")
return 1

def pre_hook(script, fillable):
locPython = getPython()

if locPython == 1:
return 1

try:
output = subprocess.run([locPython, script, "-f", fillable], capture_output=True, check=True)
return output.stdout.decode("utf-8")
except subprocess.CalledProcessError as e:
FppmUtils.print_error(f"[ERR]: Error running pre-hook script: {e}")
return 1

def post_hook(script, generated):
locPython = getPython()

if locPython == 1:
return 1

try:
output = subprocess.run([locPython, script, "-g", generated], capture_output=True, check=True)
return output.stdout.decode("utf-8")
except subprocess.CalledProcessError as e:
FppmUtils.print_error(f"[ERR]: Error running post-hook script: {e}")
return 1

0 comments on commit f3350f1

Please sign in to comment.