Started work on devel CLI commands
For now only devel:newplugin is available (if cookiecutter is installed)
This commit is contained in:
parent
2dd04574ce
commit
a6f11b9744
3 changed files with 119 additions and 1 deletions
|
|
@ -49,6 +49,7 @@ def hidden_option(*param_decls, **attrs):
|
|||
|
||||
from .server import server_commands
|
||||
from .plugins import plugin_commands
|
||||
from .devel import devel_commands
|
||||
|
||||
def set_ctx_obj_option(ctx, param, value):
|
||||
"""Helper for setting eager options on the context."""
|
||||
|
|
@ -60,7 +61,7 @@ def set_ctx_obj_option(ctx, param, value):
|
|||
|
||||
|
||||
@click.group(name="octoprint", invoke_without_command=True, cls=click.CommandCollection,
|
||||
sources=[server_commands, plugin_commands])
|
||||
sources=[server_commands, plugin_commands, devel_commands])
|
||||
@click.option("--basedir", "-b", type=click.Path(), callback=set_ctx_obj_option, is_eager=True,
|
||||
help="Specify the basedir to use for uploads, timelapses etc.")
|
||||
@click.option("--config", "-c", "configfile", type=click.Path(), callback=set_ctx_obj_option, is_eager=True,
|
||||
|
|
|
|||
75
src/octoprint/cli/devel.py
Normal file
75
src/octoprint/cli/devel.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# coding=utf-8
|
||||
from __future__ import absolute_import
|
||||
|
||||
__author__ = "Gina Häußge <osd@foosel.net>"
|
||||
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
|
||||
__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License"
|
||||
|
||||
|
||||
import click
|
||||
import logging
|
||||
|
||||
|
||||
class OctoPrintDevelCommands(click.MultiCommand):
|
||||
|
||||
sep = ":"
|
||||
|
||||
def list_commands(self, ctx):
|
||||
result = [name for name in self._get_commands()]
|
||||
result.sort()
|
||||
return result
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
commands = self._get_commands()
|
||||
return commands.get(cmd_name, None)
|
||||
|
||||
def _get_commands(self):
|
||||
commands = dict()
|
||||
|
||||
for name in [x for x in dir(self) if x.startswith("command_")]:
|
||||
method = getattr(self, name)
|
||||
|
||||
try:
|
||||
result = method()
|
||||
if result is not None:
|
||||
commands["devel" + self.sep + result.name] = result
|
||||
except:
|
||||
logging.getLogger(__name__).exception("There was an error registering one of the devel commands ({})".format(name))
|
||||
|
||||
return commands
|
||||
|
||||
def command_newplugin(self):
|
||||
try:
|
||||
import cookiecutter.main
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
import contextlib
|
||||
|
||||
@contextlib.contextmanager
|
||||
def custom_cookiecutter_config(config):
|
||||
from octoprint.util import fallback_dict
|
||||
|
||||
original_get_user_config = cookiecutter.main.get_user_config
|
||||
original_config = original_get_user_config()
|
||||
try:
|
||||
cookiecutter.main.get_user_config = lambda: fallback_dict(config, original_config)
|
||||
yield
|
||||
finally:
|
||||
cookiecutter.main.get_user_config = original_get_user_config
|
||||
|
||||
@click.command("newplugin")
|
||||
def command():
|
||||
"""Creates a new plugin based on the OctoPrint Plugin cookiecutter template."""
|
||||
from octoprint.util import tempdir
|
||||
|
||||
with tempdir() as path:
|
||||
custom = dict(cookiecutters_dir=path)
|
||||
with custom_cookiecutter_config(custom):
|
||||
cookiecutter.main.cookiecutter("gh:OctoPrint/cookiecutter-octoprint-plugin")
|
||||
|
||||
return command
|
||||
|
||||
@click.group(cls=OctoPrintDevelCommands)
|
||||
def devel_commands():
|
||||
pass
|
||||
|
|
@ -547,6 +547,36 @@ def dict_contains_keys(keys, dictionary):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
class fallback_dict(dict):
|
||||
def __init__(self, custom, *fallbacks):
|
||||
self.custom = custom
|
||||
self.fallbacks = fallbacks
|
||||
|
||||
def __getitem__(self, item):
|
||||
for dictionary in self._all():
|
||||
if item in dictionary:
|
||||
return dictionary[item]
|
||||
raise KeyError()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.custom[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
for dictionary in self._all():
|
||||
if key in dictionary:
|
||||
del dictionary[key]
|
||||
|
||||
def keys(self):
|
||||
result = set()
|
||||
for dictionary in self._all():
|
||||
result += dictionary.keys()
|
||||
return result
|
||||
|
||||
def _all(self):
|
||||
return [self.custom] + list(self.fallbacks)
|
||||
|
||||
|
||||
class Object(object):
|
||||
pass
|
||||
|
||||
|
|
@ -594,6 +624,18 @@ def atomic_write(filename, mode="w+b", prefix="tmp", suffix=""):
|
|||
shutil.move(temp_config.name, filename)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tempdir(**kwargs):
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
dirpath = tempfile.mkdtemp(**kwargs)
|
||||
try:
|
||||
yield dirpath
|
||||
finally:
|
||||
shutil.rmtree(dirpath)
|
||||
|
||||
|
||||
def bom_aware_open(filename, encoding="ascii", mode="r", **kwargs):
|
||||
import codecs
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue