# -*- coding: utf-8 -*-
"""Console script for luci."""
import logging
import sys
import click
import click_completion
import click_log
from . import vars_file
from .cli_lucifier import CliLucifier
from .defaults import *
from .finders import LucifierFinder
from .exceptions import NoSuchDictletException
from frutils.defaults import KEY_DICTLET_LUCIFIER_NAME
# needs to be done here, otherwise there might be a circular import...
# noinspection ES6UnusedImports
log = logging.getLogger("lucify")
click_log.basic_config(log)
# optional shell completion
click_completion.init()
[docs]class LuciCommand(click.MultiCommand):
"""Class to create and wrap around any :class:`~luci.lucifiers.Lucifier`, with added features to generate a command-line interface for each of them..
This class extends :class:`click.MultiCommand`. It uses a special Lucifier, :class:`~luci.cli_lucifier.CliLucifier` to hold the Lucifier, investigate it's metadata and create the cli in a way click understands.
"""
def __init__(self, **kwargs):
super(LuciCommand, self).__init__(**kwargs)
self.lucifier_finder = LucifierFinder()
[docs] def list_commands(self, ctx):
"""Lists all registered Lucifiers.
`stevedore <https://docs.openstack.org/stevedore/latest/>`_ is used to load the registered plugins. In order to register a plugin, you need to create a class that extends :class:`~luci.lucifiers.Lucifier`, and you needs to specify it like below in a packages' ``setup.py``::
entry_points={
'luci.lucifiers': [
'metadata=luci.lucifiers:MetadataLucifier'
]
}
"""
result = self.lucifier_finder.get_all_dictlets().keys()
return sorted(result)
[docs] def get_command(self, ctx, name):
"""Returns the :meth:`~luci.lucifiers.Lucifier.process` function of a wrapped Lucifier object."""
try:
log.debug("loading lucifier: {}".format(name))
dictlet_path = None
try:
lucifier_details = self.lucifier_finder.get_dictlet_details(name)
except (NoSuchDictletException) as e:
log.debug("Could not load lucifier '{}', trying file...".format(name))
if not os.path.exists(name):
raise click.ClickException(
"Could not determine which lucifier to use for '{}'".format(
name
)
)
# TODO check for folder
lucifier_names = self.list_commands(ctx)
lucifier_to_use = None
with open(name) as dictlet:
for line in dictlet:
if KEY_DICTLET_LUCIFIER_NAME in line:
for ln in lucifier_names:
if ln in line:
lucifier_to_use = ln
break
if not lucifier_to_use:
raise click.ClickException(
"Could not determine with lucifier to use for dictlet '{}'".format(
name
)
)
log.debug("Found lucifier to use: {}".format(lucifier_to_use))
dictlet_path = name
name = lucifier_to_use
lucifier_details = self.lucifier_finder.get_dictlet_details(name)
log.debug(" -> lucifier details: {}".format(lucifier_details))
lucifier = CliLucifier(ctx=ctx, dictlet=dictlet_path)
lucifier.overlay_dictlet(name, lucifier_details, add_dictlet=True)
lucifier_command = lucifier.process()
if not len(lucifier_command) == 1:
raise Exception("Need exactly 1 command to proceed.")
cmd = list(lucifier_command.values())[0]
if dictlet_path:
return cmd()
else:
return cmd
except (click.ClickException) as e:
raise e
except Exception as e:
log.exception("Error loading dictlet '{}'".format(name))
click.echo("Could not load dictlet '{}': {}".format(name, e))
return None
VARS_HELP = (
"variables to be used for templating, can be overridden by cli options if applicable"
)
DEFAULTS_HELP = "default variables, can be used to seed a lucifying process"
KEEP_METADATA_HELP = "keep metadata in result directory, mostly useful for debugging"
# click.core.SUBCOMMAND_METAVAR = 'LUCIFIER [LUCIFIER_PARAMS]...'
@click.command(cls=LuciCommand, epilog=LUCI_EPILOG_TEXT, subcommand_metavar="LUCIFIER")
@click.option(
"--defaults",
"-d",
required=False,
multiple=True,
help=DEFAULTS_HELP,
type=vars_file,
metavar="VARS",
)
@click_log.simple_verbosity_option(log, "--verbosity")
@click.pass_context
def cli(ctx, defaults):
"""luci is a generic, plug-able command-line metadata extractor and interpreter.
It is build upon the `luci <https://github.com/makkus/luci` Python library. It's interface works via sub-commands (similar to git), each first-level sub-command being called a 'lucifier', which determines the task that is to be executed.
The second-level sub-command is called a 'dictlet' (usually a file, but could also be a folder or even url), which contains some sort of metadata (either directly via yaml/json text content, or it's structure, or some other way). This metadata can be used directly, and/or to generate additional, dictlet-specific command-line options so users can add more metadata at run-time. The final metadata which used in the task is a merged dictionary of all metadata that was added to the dictlet (e.g. via the ``luci --defaults`` option, or command-line input to the lucifier or dictlet, or other means).
Find a list of available lucifiers below.
"""
pass
if __name__ == "__main__":
sys.exit(cli()) # pragma: no cover