Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat phal plugins #22

Merged
merged 2 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,725 changes: 1,628 additions & 97 deletions API.md

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Add that to your `~/.bashrc` or `~/.zshrc` file to make it permanent. Be sure to

In a new directory, run:

`projen new ovosskill --from "@mikejgray/ovos-skill-projen@latest"`
`npx projen new ovosskill --from "@mikejgray/ovos-skill-projen@latest"`

**NOTE**: This repo is not yet available on NPM. Please add the following to your `~/.npmrc` file (create one if it doesn't exist), with [a GitHub token that has packages:read permissions](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry):

Expand Down Expand Up @@ -52,6 +52,26 @@ Example:

After editing `.projenrc.json`, run `pj` to regenerate the project files. This can automatically keep your project up to date with the latest changes, including managing your `setup.py` file.

## Create a new PHAL plugin template

In a new directory, run:

`npx projen new ovosphal --from "@mikejgray/ovos-skill-projen@latest"`

The instructions are the same as for creating a new skill template, except that you have an additional option to set the PHAL plugin as admin or not. The default is `false`, indicating that the PHAL plugin will not run as root.

**NOTE:** If you do set the PHAL plugin to `admin` and thus run as root, you will also need to update your OVOS config to explicitly allow your root plugins. This is a security risk and should only be done if you understand the implications. For more information about Admin PHAL, [see the OVOS technical manual](https://openvoiceos.github.io/ovos-technical-manual/PHAL/#admin-phal).

Example OVOS config:

```json
{
"PHAL": {
"admin": "ovos-phal-plugin-helloworld": {"enabled": true}
}
}
```

### setup.py ownership

Note that projen takes ownership of the `setup.py` file, and the expectation is that manual edits are not allowed. If you need to make changes to the `setup.py` file, you should do so by editing `.projenrc.json` and running `pj` to regenerate the file.
Expand Down
24 changes: 24 additions & 0 deletions src/files/README.phal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
interface readmePhalInterface {
skillClass: string;
authorName: string;
authorHandle: string;
};

export const readmePhalMd = ({
skillClass,
authorName,
authorHandle,
}: readmePhalInterface): string => {
return `# ${skillClass}

Template for an OVOS PHAL plugin.

\`\`\`python
self.bus.on("mycroft.ready", self.on_mycroft_ready)
\`\`\`

## Credits

${authorName} (@${authorHandle})
`;
};
28 changes: 28 additions & 0 deletions src/files/__init__.phal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from ovos_bus_client import Message
from ovos_plugin_manager.phal import PHALPlugin


class HelloWorldPlugin(PHALPlugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bus.on("mycroft.ready", self.on_mycroft_ready)

@property
def my_setting(self):
"""Dynamically get the my_setting from the config file(s).
Example:
{
"PHAL": {
"ovos-phal-plugin-helloworld": {
"my_setting": "my_value"
}
}
}
If it doesn't exist, return the default value.
"""
return self.config.get("my_setting", "default_value")

def on_mycroft_ready(self, message: Message):
"""Take action when OVOS is ready."""
self.log.info("OVOS reported ready, now I can do something!")
# Implement something here
135 changes: 135 additions & 0 deletions src/files/setup.py.phal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
interface setupPyInterface {
/**
* The URL of the plugin's GitHub repository.
*/
repositoryUrl: string;
/**
* The name of the directory containing the plugin's code.
* @default "" (root)
*/
packageDir: string;
/**
* The name of the plugin's author.
*/
author: string;
/**
* The email address of the plugin's author.
*/
authorAddress: string;
/**
* The name of the plugin class.
*/
pluginClass: string;
/**
* The name of the plugin's PyPi package.
*/
pypiName: string;
/**
* The description of the plugin.
*/
description: string;
/**
* The license of the plugin.
*/
license: string;
/**
* Whether the plugin is an admin PHAL plugin, meaning it runs as root.
* @default false
*/
admin?: boolean;
}

export const setupPhalPy = ({
repositoryUrl,
packageDir,
author,
authorAddress,
pypiName,
pluginClass,
description,
license,
admin,
}: setupPyInterface): string => {
return `#!/usr/bin/env python3
from setuptools import setup
from os import walk, path

BASEDIR = path.abspath(path.dirname(__file__))
URL = "${repositoryUrl}"
plugin_CLAZZ = "${pluginClass}" # needs to match __init__.py class name
PYPI_NAME = "${pypiName}" # pip install PYPI_NAME

# below derived from github url to ensure standard plugin_id
plugin_AUTHOR, plugin_NAME = URL.split(".com/")[-1].split("/")
plugin_PKG = plugin_NAME.lower().replace("-", "_")
PLUGIN_ENTRY_POINT = f"{plugin_NAME.lower()}.{plugin_AUTHOR.lower()}={plugin_PKG}:{plugin_CLAZZ}"
# plugin_id=package_name:pluginClass
BASE_PATH = BASE_PATH = path.abspath(path.join(path.dirname(__file__), "${packageDir}"))


def get_version():
"""Find the version of the package"""
version = None
version_file = path.join(BASE_PATH, "version.py")
major, minor, build, alpha = (None, None, None, None)
with open(version_file) as f:
for line in f:
if "VERSION_MAJOR" in line:
major = line.split("=")[1].strip()
elif "VERSION_MINOR" in line:
minor = line.split("=")[1].strip()
elif "VERSION_BUILD" in line:
build = line.split("=")[1].strip()
elif "VERSION_ALPHA" in line:
alpha = line.split("=")[1].strip()

if (major and minor and build and alpha) or "# END_VERSION_BLOCK" in line:
break
version = f"{major}.{minor}.{build}"
if alpha and int(alpha) > 0:
version += f"a{alpha}"
return version


def get_requirements(requirements_filename: str):
requirements_file = path.join(path.dirname(__file__), requirements_filename)
with open(requirements_file, "r", encoding="utf-8") as r:
requirements = r.readlines()
requirements = [r.strip() for r in requirements if r.strip() and not r.strip().startswith("#")]
return requirements


def find_resource_files():
resource_base_dirs = ("locale", "intents", "dialog", "vocab", "regex", "ui")
package_data = ["*.json"]
for res in resource_base_dirs:
if path.isdir(path.join(BASE_PATH, res)):
for directory, _, files in walk(path.join(BASE_PATH, res)):
if files:
package_data.append(path.join(directory.replace(BASE_PATH, "").lstrip("/"), "*"))
return package_data


with open("README.md", "r") as f:
long_description = f.read()

setup(
name=PYPI_NAME,
version=get_version(),
description="${description}",
long_description=long_description,
long_description_content_type="text/markdown",
url=URL,
author="${author}",
author_email="${authorAddress}",
license="${license}",
package_dir={plugin_PKG: "${packageDir ?? '.'}"},
package_data={plugin_PKG: find_resource_files()},
packages=[plugin_PKG],
include_package_data=True,
install_requires=get_requirements("requirements.txt"),
keywords="ovos plugin voice assistant",
entry_points={${admin ? 'ovos.plugin.phal.admin' : 'ovos.plugin.phal'}: PLUGIN_ENTRY_POINT},
)
`;
};
2 changes: 1 addition & 1 deletion src/files/setup.py.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface setupPyInterface {
export interface setupPyInterface {
/**
* The URL of the skill's GitHub repository.
*/
Expand Down
Loading
Loading