Add more stuff

This commit is contained in:
Tulir Asokan 2018-12-13 18:15:24 +02:00
parent 8e2f2908a6
commit 7816212190
16 changed files with 677 additions and 63 deletions

View File

@ -38,24 +38,24 @@ parser.add_argument("-b", "--base-config", type=str, default="example-config.yam
"(for automatic config updates)") "(for automatic config updates)")
args = parser.parse_args() args = parser.parse_args()
config = Config(args.config, args.base_config) base_config = Config(args.config, args.base_config)
config.load() base_config.load()
config.update() base_config.update()
logging.config.dictConfig(copy.deepcopy(config["logging"])) logging.config.dictConfig(copy.deepcopy(base_config["logging"]))
init_log_listener() init_log_listener()
log = logging.getLogger("maubot.init") log = logging.getLogger("maubot.init")
log.info(f"Initializing maubot {__version__}") log.info(f"Initializing maubot {__version__}")
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
init_zip_loader(config) init_zip_loader(base_config)
db_session = init_db(config) db_session = init_db(base_config)
clients = init_client_class(db_session, loop) clients = init_client_class(db_session, loop)
plugins = init_plugin_instance_class(db_session, config, loop) plugins = init_plugin_instance_class(db_session, base_config, loop)
management_api = init_mgmt_api(config, loop) management_api = init_mgmt_api(base_config, loop)
server = MaubotServer(config, loop) server = MaubotServer(base_config, loop)
server.app.add_subapp(config["server.base_path"], management_api) server.app.add_subapp(base_config["server.base_path"], management_api)
for plugin in plugins: for plugin in plugins:
plugin.load() plugin.load()

View File

@ -14,17 +14,17 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import click import click
import os
from ..base import app from ..base import app
from ..util import type_path from ..util.validators import PathValidator
@app.command(short_help="Build a maubot plugin", @app.command(short_help="Build a maubot plugin",
help="Build a maubot plugin. First parameter is the path to root of the plugin " help="Build a maubot plugin. First parameter is the path to root of the plugin "
"to build. You can also use --output to specify output file.") "to build. You can also use --output to specify output file.")
@click.argument("path", default=".") @click.argument("path", default=".")
@click.option("-o", "--output", help="Path to output built plugin to", type=type_path) @click.option("-o", "--output", help="Path to output built plugin to",
type=PathValidator.click_type)
@click.option("-u", "--upload", help="Upload plugin to main server after building", is_flag=True, @click.option("-u", "--upload", help="Upload plugin to main server after building", is_flag=True,
default=False) default=False)
def build(path: str, output: str, upload: bool) -> None: def build(path: str, output: str, upload: bool) -> None:

View File

@ -13,22 +13,54 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import click from pkg_resources import resource_string
import os import os
from ..base import app from packaging.version import Version
from ..util import type_path from jinja2 import Template
from ..util.validators import SPDXValidator, VersionValidator
from ..util import clickquiry
loaded: bool = False
meta_template: Template
mod_template: Template
base_config: str
@app.command(help="Initialize a new maubot plugin") def load_templates():
@click.option("-n", "--name", help="The name of the project", default=os.path.basename(os.getcwd()), global mod_template, meta_template, base_config, loaded
prompt=True, show_default="directory name") if loaded:
@click.option("-i", "--id", help="The maubot plugin ID (Java package name format)", prompt=True) return
@click.option("-v", "--version", help="Initial version for project", default="0.1.0", meta_template = Template(resource_string("maubot.cli", "res/maubot.yaml.j2").decode("utf-8"))
show_default=True) mod_template = Template(resource_string("maubot.cli", "res/plugin.py.j2").decode("utf-8"))
@click.option("-l", "--license", help="The SPDX license identifier of the license for the project", base_config = resource_string("maubot.cli", "res/config.yaml").decode("utf-8")
prompt=True, default="AGPL-3.0-or-later") loaded = True
@click.option("-c", "--config", help="Include a config in the plugin stub", is_flag=True,
default=False)
def init(name: str, id: str, version: str, license: str, config: bool) -> None: @clickquiry.command(help="Initialize a new maubot plugin")
pass @clickquiry.option("-n", "--name", help="The name of the project", required=True,
default=os.path.basename(os.getcwd()))
@clickquiry.option("-i", "--id", message="ID", required=True,
help="The maubot plugin ID (Java package name format)")
@clickquiry.option("-v", "--version", help="Initial version for project (PEP-440 format)",
default="0.1.0", validator=VersionValidator, required=True)
@clickquiry.option("-l", "--license", validator=SPDXValidator, default="AGPL-3.0-or-later",
help="The license for the project (SPDX identifier)", required=False)
@clickquiry.option("-c", "--config", message="Should the plugin include a config?",
help="Include a config in the plugin stub", is_flag=True, default="null")
def init(name: str, id: str, version: Version, license: str, config: bool) -> None:
load_templates()
main_class = name[0].upper() + name[1:]
meta = meta_template.render(id=id, version=str(version), license=license, config=config,
main_class=main_class)
with open("maubot.yaml", "w") as file:
file.write(meta)
if not os.path.isdir(name):
os.mkdir(name)
mod = mod_template.render(config=config, name=main_class)
with open(f"{name}/__init__.py", "w") as file:
file.write(mod)
if config:
with open("base-config.yaml", "w") as file:
file.write(base_config)

View File

@ -15,22 +15,19 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from urllib.request import urlopen from urllib.request import urlopen
from urllib.error import HTTPError from urllib.error import HTTPError
import click
import json import json
import os import os
from colorama import Fore, Style from colorama import Fore, Style
from maubot.cli.base import app from ..config import save_config, config
from maubot.cli.config import save_config, config from ..util import clickquiry
@app.command(help="Log in to a Maubot instance") @clickquiry.command(help="Log in to a Maubot instance")
@click.argument("server", required=True, default="http://localhost:29316") @clickquiry.option("-u", "--username", help="The username of your account", default=os.environ.get("USER", None), required=True)
@click.option("-u", "--username", help="The username of your account", prompt=True, @clickquiry.option("-p", "--password", help="The password to your account", inq_type="password", required=True)
default=lambda: os.environ.get('USER', ''), show_default="current user") @clickquiry.option("-s", "--server", help="The server to log in to", default="http://localhost:29316", required=True)
@click.password_option("-p", "--password", help="The password to your account", required=True,
confirmation_prompt=False)
def login(server, username, password) -> None: def login(server, username, password) -> None:
data = { data = {
"username": username, "username": username,
@ -42,7 +39,7 @@ def login(server, username, password) -> None:
resp = json.load(resp_data) resp = json.load(resp_data)
config["servers"][server] = resp["token"] config["servers"][server] = resp["token"]
save_config() save_config()
print(Fore.GREEN, "Logged in successfully") print(Fore.GREEN + "Logged in successfully")
except HTTPError as e: except HTTPError as e:
if e.code == 401: if e.code == 401:
print(Fore.RED + "Invalid username or password" + Style.RESET_ALL) print(Fore.RED + "Invalid username or password" + Style.RESET_ALL)

View File

@ -14,9 +14,8 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import click import click
import os
from maubot.cli.base import app from ..base import app
@app.command(help="Upload a maubot plugin") @app.command(help="Upload a maubot plugin")

View File

@ -0,0 +1,42 @@
# The unique ID for the plugin. Java package naming style. (i.e. use your own domain, not xyz.maubot)
id: {{ id }}
# A PEP 440 compliant version string.
version: {{ version }}
# The SPDX license identifier for the plugin. https://spdx.org/licenses/
# Optional, assumes all rights reserved if omitted.
{% if license %}
license: {{ license }}
{% else %}
#license: null
{% endif %}
# The list of modules to load from the plugin archive.
# Modules can be directories with an __init__.py file or simply python files.
# Submodules that are imported by modules listed here don't need to be listed separately.
# However, top-level modules must always be listed even if they're imported by other modules.
modules:
- {{ name }}
# The main class of the plugin. Format: module/Class
# If `module` is omitted, will default to last module specified in the module list.
# Even if `module` is not omitted here, it must be included in the modules list.
# The main class must extend maubot.Plugin
main_class: {{ main_class }}
# Extra files that the upcoming build tool should include in the mbp file.
{% if config %}
extra_files:
- base-config.yaml
{% else %}
#extra_files:
#- base-config.yaml
{% endif %}
# List of dependencies
#dependencies:
#- foo
#soft_dependencies:
#- bar>=0.1

View File

@ -1,4 +1,5 @@
from maubot import Plugin from maubot import Plugin
{% if config %}
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
class Config(BaseProxyConfig): class Config(BaseProxyConfig):
@ -6,15 +7,22 @@ class Config(BaseProxyConfig):
helper.copy("example_1") helper.copy("example_1")
helper.copy("example_2.list") helper.copy("example_2.list")
helper.copy("example_2.value") helper.copy("example_2.value")
{% endif %}
class {{ name }}: class {{ name }}:
async def start() -> None: async def start() -> None:
{% if config %}
self.config.load_and_update() self.config.load_and_update()
self.log.debug("Loaded %s from config example 2", self.config["example_2.value"]) self.log.debug("Loaded %s from config example 2", self.config["example_2.value"])
{% else %}
pass
{% endif %}
async def stop() -> None: async def stop() -> None:
pass pass
{% if config %}
@classmethod @classmethod
def get_config_class(cls) -> Type[BaseProxyConfig]: def get_config_class(cls) -> Type[BaseProxyConfig]:
return Config return Config
{% endif %}

View File

@ -0,0 +1,383 @@
[
"0BSD",
"AAL",
"Abstyles",
"Adobe-2006",
"Adobe-Glyph",
"ADSL",
"AFL-1.1",
"AFL-1.2",
"AFL-2.0",
"AFL-2.1",
"AFL-3.0",
"Afmparse",
"AGPL-1.0-only",
"AGPL-1.0-or-later",
"AGPL-3.0-only",
"AGPL-3.0-or-later",
"Aladdin",
"AMDPLPA",
"AML",
"AMPAS",
"ANTLR-PD",
"Apache-1.0",
"Apache-1.1",
"Apache-2.0",
"APAFML",
"APL-1.0",
"APSL-1.0",
"APSL-1.1",
"APSL-1.2",
"APSL-2.0",
"Artistic-1.0-cl8",
"Artistic-1.0-Perl",
"Artistic-1.0",
"Artistic-2.0",
"Bahyph",
"Barr",
"Beerware",
"BitTorrent-1.0",
"BitTorrent-1.1",
"Borceux",
"BSD-1-Clause",
"BSD-2-Clause-FreeBSD",
"BSD-2-Clause-NetBSD",
"BSD-2-Clause-Patent",
"BSD-2-Clause",
"BSD-3-Clause-Attribution",
"BSD-3-Clause-Clear",
"BSD-3-Clause-LBNL",
"BSD-3-Clause-No-Nuclear-License-2014",
"BSD-3-Clause-No-Nuclear-License",
"BSD-3-Clause-No-Nuclear-Warranty",
"BSD-3-Clause",
"BSD-4-Clause-UC",
"BSD-4-Clause",
"BSD-Protection",
"BSD-Source-Code",
"BSL-1.0",
"bzip2-1.0.5",
"bzip2-1.0.6",
"Caldera",
"CATOSL-1.1",
"CC-BY-1.0",
"CC-BY-2.0",
"CC-BY-2.5",
"CC-BY-3.0",
"CC-BY-4.0",
"CC-BY-NC-1.0",
"CC-BY-NC-2.0",
"CC-BY-NC-2.5",
"CC-BY-NC-3.0",
"CC-BY-NC-4.0",
"CC-BY-NC-ND-1.0",
"CC-BY-NC-ND-2.0",
"CC-BY-NC-ND-2.5",
"CC-BY-NC-ND-3.0",
"CC-BY-NC-ND-4.0",
"CC-BY-NC-SA-1.0",
"CC-BY-NC-SA-2.0",
"CC-BY-NC-SA-2.5",
"CC-BY-NC-SA-3.0",
"CC-BY-NC-SA-4.0",
"CC-BY-ND-1.0",
"CC-BY-ND-2.0",
"CC-BY-ND-2.5",
"CC-BY-ND-3.0",
"CC-BY-ND-4.0",
"CC-BY-SA-1.0",
"CC-BY-SA-2.0",
"CC-BY-SA-2.5",
"CC-BY-SA-3.0",
"CC-BY-SA-4.0",
"CC0-1.0",
"CDDL-1.0",
"CDDL-1.1",
"CDLA-Permissive-1.0",
"CDLA-Sharing-1.0",
"CECILL-1.0",
"CECILL-1.1",
"CECILL-2.0",
"CECILL-2.1",
"CECILL-B",
"CECILL-C",
"ClArtistic",
"CNRI-Jython",
"CNRI-Python-GPL-Compatible",
"CNRI-Python",
"Condor-1.1",
"copyleft-next-0.3.1",
"CPAL-1.0",
"CPL-1.0",
"CPOL-1.02",
"Crossword",
"CrystalStacker",
"CUA-OPL-1.0",
"Cube",
"curl",
"D-FSL-1.0",
"diffmark",
"DOC",
"Dotseqn",
"DSDP",
"dvipdfm",
"ECL-1.0",
"ECL-2.0",
"EFL-1.0",
"EFL-2.0",
"eGenix",
"Entessa",
"EPL-1.0",
"EPL-2.0",
"ErlPL-1.1",
"EUDatagrid",
"EUPL-1.0",
"EUPL-1.1",
"EUPL-1.2",
"Eurosym",
"Fair",
"Frameworx-1.0",
"FreeImage",
"FSFAP",
"FSFUL",
"FSFULLR",
"FTL",
"GFDL-1.1-only",
"GFDL-1.1-or-later",
"GFDL-1.2-only",
"GFDL-1.2-or-later",
"GFDL-1.3-only",
"GFDL-1.3-or-later",
"Giftware",
"GL2PS",
"Glide",
"Glulxe",
"gnuplot",
"GPL-1.0-only",
"GPL-1.0-or-later",
"GPL-2.0-only",
"GPL-2.0-or-later",
"GPL-3.0-only",
"GPL-3.0-or-later",
"gSOAP-1.3b",
"HaskellReport",
"HPND",
"IBM-pibs",
"ICU",
"IJG",
"ImageMagick",
"iMatix",
"Imlib2",
"Info-ZIP",
"Intel-ACPI",
"Intel",
"Interbase-1.0",
"IPA",
"IPL-1.0",
"ISC",
"JasPer-2.0",
"JSON",
"LAL-1.2",
"LAL-1.3",
"Latex2e",
"Leptonica",
"LGPL-2.0-only",
"LGPL-2.0-or-later",
"LGPL-2.1-only",
"LGPL-2.1-or-later",
"LGPL-3.0-only",
"LGPL-3.0-or-later",
"LGPLLR",
"Libpng",
"libtiff",
"LiLiQ-P-1.1",
"LiLiQ-R-1.1",
"LiLiQ-Rplus-1.1",
"Linux-OpenIB",
"LPL-1.0",
"LPL-1.02",
"LPPL-1.0",
"LPPL-1.1",
"LPPL-1.2",
"LPPL-1.3a",
"LPPL-1.3c",
"MakeIndex",
"MirOS",
"MIT-0",
"MIT-advertising",
"MIT-CMU",
"MIT-enna",
"MIT-feh",
"MIT",
"MITNFA",
"Motosoto",
"mpich2",
"MPL-1.0",
"MPL-1.1",
"MPL-2.0-no-copyleft-exception",
"MPL-2.0",
"MS-PL",
"MS-RL",
"MTLL",
"Multics",
"Mup",
"NASA-1.3",
"Naumen",
"NBPL-1.0",
"NCSA",
"Net-SNMP",
"NetCDF",
"Newsletr",
"NGPL",
"NLOD-1.0",
"NLPL",
"Nokia",
"NOSL",
"Noweb",
"NPL-1.0",
"NPL-1.1",
"NPOSL-3.0",
"NRL",
"NTP",
"OCCT-PL",
"OCLC-2.0",
"ODbL-1.0",
"ODC-By-1.0",
"OFL-1.0",
"OFL-1.1",
"OGL-UK-1.0",
"OGL-UK-2.0",
"OGL-UK-3.0",
"OGTSL",
"OLDAP-1.1",
"OLDAP-1.2",
"OLDAP-1.3",
"OLDAP-1.4",
"OLDAP-2.0.1",
"OLDAP-2.0",
"OLDAP-2.1",
"OLDAP-2.2.1",
"OLDAP-2.2.2",
"OLDAP-2.2",
"OLDAP-2.3",
"OLDAP-2.4",
"OLDAP-2.5",
"OLDAP-2.6",
"OLDAP-2.7",
"OLDAP-2.8",
"OML",
"OpenSSL",
"OPL-1.0",
"OSET-PL-2.1",
"OSL-1.0",
"OSL-1.1",
"OSL-2.0",
"OSL-2.1",
"OSL-3.0",
"PDDL-1.0",
"PHP-3.0",
"PHP-3.01",
"Plexus",
"PostgreSQL",
"psfrag",
"psutils",
"Python-2.0",
"Qhull",
"QPL-1.0",
"Rdisc",
"RHeCos-1.1",
"RPL-1.1",
"RPL-1.5",
"RPSL-1.0",
"RSA-MD",
"RSCPL",
"Ruby",
"SAX-PD",
"Saxpath",
"SCEA",
"Sendmail-8.23",
"Sendmail",
"SGI-B-1.0",
"SGI-B-1.1",
"SGI-B-2.0",
"SimPL-2.0",
"SISSL-1.2",
"SISSL",
"Sleepycat",
"SMLNJ",
"SMPPL",
"SNIA",
"Spencer-86",
"Spencer-94",
"Spencer-99",
"SPL-1.0",
"SugarCRM-1.1.3",
"SWL",
"TCL",
"TCP-wrappers",
"TMate",
"TORQUE-1.1",
"TOSL",
"TU-Berlin-1.0",
"TU-Berlin-2.0",
"Unicode-DFS-2015",
"Unicode-DFS-2016",
"Unicode-TOU",
"Unlicense",
"UPL-1.0",
"Vim",
"VOSTROM",
"VSL-1.0",
"W3C-19980720",
"W3C-20150513",
"W3C",
"Watcom-1.0",
"Wsuipa",
"WTFPL",
"X11",
"Xerox",
"XFree86-1.1",
"xinetd",
"Xnet",
"xpp",
"XSkat",
"YPL-1.0",
"YPL-1.1",
"Zed",
"Zend-2.0",
"Zimbra-1.3",
"Zimbra-1.4",
"zlib-acknowledgement",
"Zlib",
"ZPL-1.1",
"ZPL-2.0",
"ZPL-2.1",
"AGPL-1.0",
"AGPL-3.0",
"eCos-2.0",
"GFDL-1.1",
"GFDL-1.2",
"GFDL-1.3",
"GPL-1.0+",
"GPL-1.0",
"GPL-2.0+",
"GPL-2.0-with-autoconf-exception",
"GPL-2.0-with-bison-exception",
"GPL-2.0-with-classpath-exception",
"GPL-2.0-with-font-exception",
"GPL-2.0-with-GCC-exception",
"GPL-2.0",
"GPL-3.0+",
"GPL-3.0-with-autoconf-exception",
"GPL-3.0-with-GCC-exception",
"GPL-3.0",
"LGPL-2.0+",
"LGPL-2.0",
"LGPL-2.1+",
"LGPL-2.1",
"LGPL-3.0+",
"LGPL-3.0",
"Nunit",
"StandardML-NJ",
"wxWindows"
]

View File

@ -1,8 +0,0 @@
from maubot import Plugin
class {{ name }}:
async def start() -> None:
pass
async def stop() -> None:
pass

View File

@ -1 +0,0 @@
from .path import type_path

View File

@ -0,0 +1,77 @@
# maubot - A plugin-based Matrix bot system.
# Copyright (C) 2018 Tulir Asokan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Any, Callable, Union
import functools
from prompt_toolkit.validation import Validator
from PyInquirer import prompt
import click
from ..base import app
from .validators import Required
def command(help: str) -> Callable[[Callable], Callable]:
def decorator(func) -> Callable:
questions = func.__inquirer_questions__.copy()
@functools.wraps(func)
def wrapper(*args, **kwargs):
for key, value in kwargs.items():
if value is not None and (questions[key]["type"] != "confirm" or value != "null"):
questions.pop(key, None)
question_list = list(questions.values())
question_list.reverse()
resp = prompt(question_list, keyboard_interrupt_msg="Aborted!")
if not resp and question_list:
return
kwargs = {**kwargs, **resp}
func(*args, **kwargs)
return app.command(help=help)(wrapper)
return decorator
def option(short: str, long: str, message: str = None, help: str = None,
click_type: Union[str, Callable[[str], Any]] = None, inq_type: str = None,
validator: Validator = None, required: bool = False, default: str = None,
is_flag: bool = False) -> Callable[[Callable], Callable]:
if not message:
message = long[2].upper() + long[3:]
def decorator(func) -> Callable:
click.option(short, long, help=help, type=validator.click_type if validator else click_type,
is_flag=is_flag)(func)
if not hasattr(func, "__inquirer_questions__"):
func.__inquirer_questions__ = {}
q = {
"type": (inq_type if isinstance(inq_type, str)
else ("input" if not is_flag
else "confirm")),
"name": long[2:],
"message": message,
}
if default is not None:
q["default"] = default
if required:
q["validator"] = Required(validator)
elif validator:
q["validator"] = validator
func.__inquirer_questions__[long[2:]] = q
return func
return decorator

View File

@ -1,14 +0,0 @@
import click
import os
def type_path(val: str) -> str:
val = os.path.abspath(val)
if os.path.exists(val):
return val
directory = os.path.dirname(val)
if not os.path.isdir(directory):
if os.path.exists(directory):
raise click.BadParameter(f"{directory} is not a directory")
raise click.BadParameter(f"{directory} does not exist")
return val

View File

@ -0,0 +1,96 @@
# maubot - A plugin-based Matrix bot system.
# Copyright (C) 2018 Tulir Asokan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Callable
import pkg_resources
import json
import os
from packaging.version import Version, InvalidVersion
from prompt_toolkit.validation import Validator, ValidationError
from prompt_toolkit.document import Document
import click
class Required(Validator):
proxy: Validator
def __init__(self, proxy: Validator = None) -> None:
self.proxy = proxy
def validate(self, document: Document) -> None:
if len(document.text) == 0:
raise ValidationError(message="This field is required")
if self.proxy:
return self.proxy.validate(document)
class ClickValidator(Validator):
click_type: Callable[[str], str] = None
@classmethod
def validate(cls, document: Document) -> None:
try:
cls.click_type(document.text)
except click.BadParameter as e:
raise ValidationError(message=e.message, cursor_position=len(document.text))
def path(val: str) -> str:
val = os.path.abspath(val)
if os.path.exists(val):
return val
directory = os.path.dirname(val)
if not os.path.isdir(directory):
if os.path.exists(directory):
raise click.BadParameter(f"{directory} is not a directory")
raise click.BadParameter(f"{directory} does not exist")
return val
class PathValidator(ClickValidator):
click_type = path
def version(val: str) -> Version:
try:
return Version(val)
except InvalidVersion as e:
raise click.BadParameter(f"{val} is not a valid PEP-440 version") from e
class VersionValidator(ClickValidator):
click_type = version
spdx_list = None
def load_spdx():
global spdx_list
spdx_data = pkg_resources.resource_stream("maubot.cli", "res/spdx-simple.json")
spdx_list = json.load(spdx_data)
def spdx(val: str) -> str:
if not spdx_list:
load_spdx()
if val not in spdx_list:
raise click.BadParameter(f"{val} is not a valid SPDX license identifier")
return val
class SPDXValidator(ClickValidator):
click_type = spdx

View File

@ -10,4 +10,5 @@ packaging
click click
colorama colorama
PyInquirer
jinja2 jinja2

View File

@ -33,6 +33,7 @@ setuptools.setup(
"click>=7,<8", "click>=7,<8",
"colorama>=0.4,<0.5", "colorama>=0.4,<0.5",
"PyInquirer>=1,<2",
"jinja2>=2,<3", "jinja2>=2,<3",
], ],
@ -57,5 +58,6 @@ setuptools.setup(
package_data={ package_data={
"maubot": ["management/frontend/build/*", "management/frontend/build/static/css/*", "maubot": ["management/frontend/build/*", "management/frontend/build/static/css/*",
"management/frontend/build/static/js/*"], "management/frontend/build/static/js/*"],
"maubot.cli": ["res/*"],
}, },
) )