Esempio n. 1
0
async def main(ctx, reactor, urls_and_or_files):
    log = create_logger(namespace="korbenware.cli.open")
    config = ctx.config

    applications = ApplicationsRegistry(config)
    mime = MimeRegistry(config, applications)
    urls = UrlRegistry(config, applications)
    finder = ApplicationFinder(urls, mime)

    # TODO: dbus executor
    executor = BaseExecutor()

    for url_or_file in urls_and_or_files:
        try:
            app = finder.get_by_url_or_file(url_or_file)
        except OpenError:
            log.error(
                "Unable to find a suitable application for opening {url_or_file}",  # noqa
                url_or_file=url_or_file,
            )
        else:
            log.info(
                "Opening {url_or_file} with application {application}...",
                url_or_file=url_or_file,
                application=app.filename,
            )
            executor.run_xdg_application(app,
                                         exec_key_fields=exec_key_fields(
                                             app, url_or_file))
Esempio n. 2
0
class MonitoringExecutor(BaseExecutor):
    log = create_logger()

    def __init__(self, reactor=None):
        super().__init__()
        self.monitor = ProcessMonitor(log=self.log, reactor=reactor)

    def start(self):
        self.log.info("Starting monitoring service...")
        self.monitor.startService()

    def stop(self):
        self.log.info("Stopping monitoring service...")
        self.monitor.stopService()

    def run_argv(
        self,
        process_name,
        argv,
        *,
        monitor=False,
        restart=False,
        cleanup=True,
        monitor_params=None,
        env=None,
        cwd=None,
    ):
        if not monitor:
            return super().run_argv(process_name, argv, env=env, cwd=cwd)

        env = load_env(env)
        cwd = cwd or os.getcwd()
        monitor_params = monitor_params or dict()

        self.log.info(
            "Adding {process_name} using {argv} as a monitored process...",  # noqa
            process_name=process_name,
            argv=argv,
            restart=restart,
            cleanup=cleanup,
            monitor_params=monitor_params,
            env=env,
            cwd=cwd,
        )

        self.monitor.addProcess(
            process_name, argv, env=env, cwd=cwd, restart=restart, cleanup=cleanup
        )

    def start_process(self, name):
        self.monitor.startProcess(name)

    def stop_process(self, name):
        self.monitor.stopProcess(name)

    def restart_process(self, name):
        self.monitor.restartProcess(name)

    def has_process(self, name):
        return self.monitor.hasProcess(name)
Esempio n. 3
0
class UrlRegistry:
    log = create_logger()

    def __init__(self, config, applications):
        self.lookup = dict()
        for scheme, desktop_file in config.urls.items():
            self.log.debug(
                "Registering desktop application {application_name} as the opener for {scheme}:// urls...",  # noqa
                application_name=desktop_file,
                scheme=scheme,
            )
            self.lookup[scheme] = desktop_file
        self.applications = applications

    def get_application_by_url(self, url):
        parsed = urllib.parse.urlparse(url)

        return self.get_application_by_scheme(parsed.scheme)

    def get_application_by_scheme(self, scheme):
        if scheme not in self.lookup:
            return None

        key = self.lookup[scheme]

        return self.applications.entries.get(key)
Esempio n. 4
0
class BaseExecutor:
    log = create_logger()

    def run_argv(
        self, process_name, argv, *, env=None, cwd=None,
    ):
        env = load_env(env)
        cwd = cwd or os.getcwd()

        self.log.info(
            "Spawning {process_name} using {argv} as a detached process...",
            process_name=process_name,
            argv=argv,
            env=env,
            cwd=cwd,
        )
        spawn_detached(argv, env=env, cwd=cwd)

    def run_config(self, process_name, config):
        kwargs = asdict(config)
        for key in list(kwargs.keys()):
            if not kwargs.get(key, None) and not isinstance(
                kwargs.get(key, None), bool
            ):
                del kwargs[key]

        return self.run_argv(process_name, **kwargs)

    def run_exec_key(
        self,
        process_name,
        exec_key,
        *,
        exec_key_fields=None,
        env=None,
        cwd=None,
        **kwargs,
    ):
        argv = exec_key.build_argv(exec_key_fields)

        self.run_argv(process_name, argv, env=env, cwd=cwd, **kwargs)

    def run_command(self, process_name, raw, **kwargs):
        return self.run_exec_key(process_name, ExecKey(raw), **kwargs)

    def run_xdg_executable(self, executable, **kwargs):
        self.log.debug(
            "Running XDG executable {filename}...",
            filename=executable.filename or ("<unknown filename>"),
        )
        return self.run_exec_key(executable.filename, executable.exec_key, **kwargs)

    def run_xdg_desktop_entry(self, entry, **kwargs):
        return self.run_xdg_executable(Executable.from_desktop_entry(entry), **kwargs)

    def run_xdg_application(self, app, **kwargs):
        return self.run_xdg_executable(app.executable, **kwargs)
Esempio n. 5
0
class ApplicationExecutor(MonitoringExecutor):
    log = create_logger()

    def __init__(self, *, reactor=None, applications=None):
        super().__init__(reactor=reactor)
        self.applications = applications

    def run_xdg_application_by_name(self, filename, **kwargs):
        self.log.debug(
            "Running XDG application {filename} by name...", filename=filename
        )
        app = self.applications.entries[filename]
        return self.run_xdg_application(app, **kwargs)
Esempio n. 6
0
class ApplicationsRegistry:
    log = create_logger()

    def __init__(self, config, key="applications", cls=Application):
        self.directories = getattr(config, key).directories
        self.entry_sets = load_application_sets(self.directories, self.log,
                                                cls)
        self.entries = dict()

        for filename, entry_set in self.entry_sets.items():
            entry = entry_set.coalesce(
                skip_unparsed=getattr(config, key).skip_unparsed,
                skip_invalid=getattr(config, key).skip_invalid,
            )
            if entry:
                self.entries[filename] = entry
Esempio n. 7
0
async def main(ctx, reactor):
    config = ctx.config

    log = create_logger(namespace="korbenware.cli.menu")

    executor = BaseExecutor()

    xdg_menu = xdg.Menu.parse(config.menu.filename)

    session = menu_session(ctx.command.hed, ctx.command.dek, xdg_menu)

    desktop_entry = await session.run()

    if not desktop_entry:
        log.info(
            "Looks like you didn't end up choosing an item from the menu; doing nothing"  # noqa
        )
    else:
        log.info("Opening {name}...", name=desktop_entry.getName())

        executor.run_xdg_desktop_entry(desktop_entry)
Esempio n. 8
0
class AutostartRegistry(ApplicationsRegistry):
    log = create_logger()

    def __init__(self, config):
        super().__init__(config, key="autostart", cls=Autostart)

        self.environment_name = config.autostart.environment_name
        self.autostart_entries = dict()

        for name, entry in self.entries.items():
            if entry.should_autostart(self.environment_name):
                self.log.debug(
                    "Entry {filename} elligible for autostart", filename=entry.filename
                )
                self.autostart_entries[entry.filename] = entry
            else:
                self.log.warn(
                    "Entry {filename} not eligible for autostart",
                    filename=entry.filename,
                    conditions=entry.autostart_conditions(self.environment_name),
                )

    def init_executor(self, executor, monitor=True, cleanup=False, env=None, cwd=None):
        for name, entry in self.autostart_entries.items():
            self.log.info(
                "Adding {name} to executor...",
                name=name,
                executor=executor,
                monitor=monitor,
                cleanup=cleanup,
                env=env,
                cwd=cwd,
            )
            executor.run_xdg_application(
                entry, monitor=monitor, cleanup=cleanup, env=env, cwd=cwd
            )
Esempio n. 9
0
    def invoke(*args, **kwargs):
        # Starting from here is much the same as click.Context.invoke...
        self, callback = args[:2]
        ctx = self

        if isinstance(callback, click.Command):
            other_cmd = callback
            callback = other_cmd.callback
            ctx = Context(other_cmd, info_name=other_cmd.name, parent=self)

            if callback is None:
                raise TypeError(
                    "The given command does not have a callback that can be invoked."
                )

            for param in other_cmd.params:
                if param.name not in kwargs and param.expose_value:
                    kwargs[param.name] = param.get_default(ctx)

        args = args[2:]

        if not iscoroutinefunction(callback):
            # In our version of invoke, we want custom logging of exits and
            # failures, so we try/except around the callback, ignoring a big
            # list of exceptions with special behavior in Click and Python,
            # and log accordingly.
            def runner():
                try:
                    rv = callback(*args, **kwargs)
                except (
                        EOFError,
                        KeyboardInterrupt,
                        SystemExit,
                        ClickException,
                        OSError,
                        Exit,
                        Abort,
                ):
                    raise
                except:  # noqa
                    self._log_failure()
                    self.exit(1)
                else:
                    self._log_ok()
                    self._run_deferred_actions()

                return rv

        else:
            # We also handle cases where the command is a Twisted coroutine -
            # in these scenarios we do basically the same thing as before,
            # except inside of a coroutine function.
            async def async_runner(*args, **kwargs):
                try:
                    rv = await callback(*args, **kwargs)
                except (
                        EOFError,
                        KeyboardInterrupt,
                        SystemExit,
                        ClickException,
                        OSError,
                        Exit,
                        Abort,
                ):
                    raise
                except:  # noqa
                    self._log_failure()
                    # Click's default exit mechanism is raising a special Exit
                    # exception, which it can't capture in an async context.
                    # Instead, we assume that its exit behaviors only matter
                    # before "async things happen" and manually exit(1).
                    sys.exit(1)
                else:
                    self._log_ok()
                    self._run_deferred_actions()

                return rv

            # This coroutine function is ran using task.react and ensureDeferred
            # - note that the return value that Click receives is that of
            # task.react and not of our coroutine. Such is life.
            def runner():
                return react(lambda reactor: ensureDeferred(
                    async_runner(reactor, *args, **kwargs)))

        # These two context managers are as in Click...
        with augment_usage_errors(self):
            with ctx:
                # If necessary, we load the korbenware config, set up a
                # logger for the context and configure a CLI observer with
                # appropriate verbosity. If this is a child context, then it
                # should already have these properties.
                if not self.config:
                    try:
                        self.config = load_config()
                        self.config_exc = None
                    except (NoConfigurationFoundError, TomlDecodeError) as exc:
                        self.config = None
                        self.config_exc = exc

                if not self.log:
                    self.log = create_logger(namespace="korbenware.cli.base")
                if not self.observer:
                    self.observer = self.command.observer_factory(
                        self.config, verbosity=kwargs.pop("verbose", None))
                    publisher.addObserver(self.observer)
                else:
                    del kwargs["verbose"]

                if not self.parent:
                    self.log.info("It worked if it ends with OK 👍")

                    greet_fields = [
                        ("hed", self.command.hed),
                        ("subhed", self.command.subhed),
                    ]

                    if self.command.dek:
                        greet_fields.append(("dek", self.command.dek))

                    max_len = max(len(value) for name, value in greet_fields)

                    self.log.info("┏━" + ("━" * max_len) + "━┓")
                    for name, value in greet_fields:
                        log_format = ("┃ {" + name + "}" +
                                      (" " * (max_len - len(value))) + " ┃")
                        self.log.info(log_format, **{name: value})
                    self.log.info("┗━" + ("━" * max_len) + "━┛")

                    if self.config_exc:
                        raise self.config_exc

                return runner()
Esempio n. 10
0
class ProcessMonitor(BaseMonitor, EventEmitter):
    """
    A subclass of twisted.runner.procmon#ProcessMonitor. While it implements
    the same interfaces, it also have a number of extensions and behavioral
    changes:

    * Processes can individually be set to restart or, crucially, *not*
      restart - this is the primary use case around the "autostart" freedesktop
      standard. The default is to not restart; it must be explicitly enabled.
    * Processes accept individual arguments for threshold, killTime,
      minRestartDelay and maxRestartDelay. The restart behavior, when enabled,
      is otherwise the same as in twisted.runner.procmon.
    * Emit events as a pyee TwistedEventEmitter for various lifecycle
      behaviors.

    Events:

    * 'startService' - The ProcessMonitor is starting.
    * 'serviceStarted' - The ProcessMonitor has stopped and all processes have
      started.
    * 'stopService' - The ProcessMonitor is stopping.
    * 'serviceStopped' - The ProcessMonitor has stopped and all processes have
      exited.
    * 'addProcess' - A process is being added.
      - state: ProcessState - The state of the newly-added process.
    * 'removeProcess' - A process is being removed.
      - state: ProcessState - The state of the process at the time of removal.
    * 'startProcess' - A process is being started.
      - state: ProcessState - The state of the process at the time of starting.
    * 'stopProcess' - A process is being stopped.
      - state: ProcessState - The state of the process at the time of it being
        stopped.
    * 'restartProcess' - A process has explicitly been told to restart. This
      event does not fire when a process exits unexpectedly, or is manually
      cycled by calls to stopProcess/startProcess.
      - state: ProcessState - the state of the process at the time of it
        restarting
    * 'connectionLost' - A process has exited.
      - state: ProcessState - the state of the process right before it exited
    * 'forceStop' - A process being stopped timed out and had to be forced to
      stop with a SIGKILL.
      - state: ProcessState - the state of the process being stopped
    * 'stateChange' - A process's state has changed.
      - state: ProcessState - the new state of the process
    """

    restart = False
    log = create_logger()

    def __init__(self, log=None, reactor=None):
        if reactor:
            BaseMonitor.__init__(self, reactor=reactor)
        else:
            BaseMonitor.__init__(self)

        EventEmitter.__init__(self)

        self.log = log or self.log
        self.settings = dict()
        self.states = dict()

    def isRegistered(self, name):
        """
        Is this process registered?
        """
        return name in self.states

    def assertRegistered(self, name):
        """
        Raises a KeyError if the process isn't registered.
        """
        if not self.isRegistered(name):
            raise KeyError(f"Unrecognized process name: {name}")

    def _setProcessState(self, name, state):
        self.states[name] = state
        self.emit("stateChange", dict(name=name, state=state))

    def getState(self, name):
        """
        Fetch and package the internal state of the process. Note that this
        will always return a ProcessState even if the internal state is
        malformed or missing.
        """
        self.assertRegistered(name)
        return ProcessState(
            name=name,
            state=self.states.get(name, None),
            settings=self.settings.get(name, None),
        )

    def addProcess(
        self,
        name,
        args,
        *,
        env=None,
        cwd=None,
        uid=None,
        gid=None,
        restart=False,
        cleanup=None,
        threshold=None,
        killTime=None,
        minRestartDelay=None,
        maxRestartDelay=None,
    ):
        """
        Add a new monitored process. If the service is running, start it
        immediately.
        """

        env = dict() if env is None else env

        if name in self.states:
            raise KeyError(f"Process {name} already exists! Try removing it first.")

        state = LifecycleState.STOPPED

        settings = ProcessSettings(restart=restart)

        if restart:
            settings.threshold = threshold if threshold is not None else self.threshold
            settings.killTime = killTime if killTime is not None else self.killTime
            settings.minRestartDelay = (
                minRestartDelay if minRestartDelay is not None else self.minRestartDelay
            )
            settings.maxRestartDelay = (
                maxRestartDelay if maxRestartDelay is not None else self.maxRestartDelay
            )
            settings.cleanup = False
        else:
            settings.cleanup = cleanup if cleanup is not None else True

        self._setProcessState(name, state)
        self.settings[name] = settings

        self.emit("addProcess", self.getState(name))

        super().addProcess(name, args, uid, gid, env, cwd)

    def removeProcess(self, name):
        """
        Remove a process. This stops the process and then removes all state
        from the process monitor.

        This currently isn't well-tested and I suspect that code paths
        triggered by stopping the process may cause async race conditions.
        It's therefore recommended that you manually stop processes first,
        before exiting.
        """
        self.emit("removeProcess", self.getState(name))
        super().removeProcess(name)
        del self.settings[name]
        del self.states[name]

    def _allServicesRunning(self):
        return all(state == LifecycleState.RUNNING for state in self.states.values())

    def startService(self):
        """
        Start the service, which starts all the processes.
        """
        self.emit("startService")
        super().startService()

        def maybe_emit(state):
            if self._allServicesRunning():
                self.remove_listener("stateChange", maybe_emit)
                self.emit("serviceStarted")

        if self._allServicesRunning():
            self.emit("serviceStarted")
        else:
            self.on("stateChange", maybe_emit)

    def _allServicesStopped(self):
        return all(state == LifecycleState.STOPPED for state in self.states.values())

    def stopService(self):
        """
        Stop the service, which stops all the processes.
        """
        self.emit("stopService")
        super().stopService()

        def maybe_emit(state):
            if self._allServicesStopped():
                self.emit("serviceStopped")

        if self._allServicesStopped():
            self.emit("serviceStopped")
        else:
            self.on("stateChange", maybe_emit)

    def _isActive(self, name):
        return self.isRegistered(name) and self.states[name] in {
            LifecycleState.RUNNING,
            LifecycleState.STOPPING,
        }

    def _spawnProcess(self, *args, **kwargs):
        return self._reactor.spawnProcess(*args, **kwargs)

    def startProcess(self, name):
        """
        Start a process. Updates the state to RUNNING.
        """
        # Unlike in procmon, we track process status in a dict so we
        # should check that to see the state

        self.assertRegistered(name)
        if self._isActive(name):
            return

        self.emit("startProcess", self.getState(name))

        # Should be smooth sailing - This section is the same as in procmon
        process = self._processes[name]

        proto = LoggingProtocol()
        proto.service = self
        proto.name = name
        self.protocols[name] = proto
        self.timeStarted[name] = self._reactor.seconds()
        self._spawnProcess(
            proto,
            process.args[0],
            process.args,
            uid=process.uid,
            gid=process.gid,
            env=process.env,
            path=process.cwd,
        )

        # This is new though!
        self._setProcessState(name, LifecycleState.RUNNING)

    def connectionLost(self, name):
        """
        Called when a monitored process exits. Overrides the base
        ProcessMonitor behavior to use per-process parameters, track state
        for external observation, and by default actually does not restart the
        process.
        """
        priorState = self.states[name]
        settings = self.settings[name]

        restartSetting = settings.restart

        # Update our state depending on what it was when the process exited
        if priorState in {LifecycleState.STARTING, LifecycleState.RUNNING}:
            # We expected the process to be running - we should fall back to
            # our individual settings for restarts
            shouldRestart = restartSetting

            # State should either be RESTARTING or STOPPED
            self.states[name] = (
                LifecycleState.RESTARTING if shouldRestart else LifecycleState.STOPPED
            )
        elif priorState == LifecycleState.RESTARTING:
            # OK, we're explicitly restarting
            shouldRestart = True
        elif priorState == LifecycleState.STOPPING:
            # OK, we're explicitly quitting
            shouldRestart = False
            self._setProcessState(name, LifecycleState.STOPPED)
        elif priorState == LifecycleState.STOPPED:
            # This shouldn't happen but if it does we *definitely* don't want
            # to restart
            shouldRestart = False

        shouldCleanup = not shouldRestart and settings.cleanup

        self.emit("connectionLost", self.getState(name))

        # This chunk is straight from procmon - this is clearing force
        # quit timeouts
        if name in self.murder:
            if self.murder[name].active():
                self.murder[name].cancel()
            del self.murder[name]

        del self.protocols[name]

        # Pulling in our per-process settings...

        threshold = settings.threshold
        minRestartDelay = settings.minRestartDelay
        maxRestartDelay = settings.maxRestartDelay

        if shouldRestart:
            # This section is also largely copied from procmon
            if self._reactor.seconds() - self.timeStarted[name] < threshold:
                nextDelay = self.delay[name]
                self.delay[name] = min(self.delay[name] * 2, maxRestartDelay)

            else:
                nextDelay = 0
                self.delay[name] = minRestartDelay

            if self.running and name in self._processes:
                self.restart[name] = self._reactor.callLater(
                    nextDelay, self.startProcess, name
                )
        elif shouldCleanup:
            # If this is a no-restart yes-cleanup process then remove it
            # on exit
            self.removeProcess(name)

    def _forceStopProcess(self, name, proc):
        self.emit("forceStop", self.getState(name))
        super()._forceStopProcess(proc)

    def hasProcess(self, name):
        """
        Check whether a process is defined with that name.
        """
        return name in self.states

    def restartProcess(self, name):
        """
        Manually restart a process, regardless of how it's been configured.
        """
        self._setProcessState(name, LifecycleState.RESTARTING)
        self.emit("restartProcess", self.getState(name))
        self._stopProcess(self, name)

    def stopProcess(self, name):
        """
        Stop a process.
        """
        self._setProcessState(name, LifecycleState.STOPPING)
        self.emit("stopProcess", self.getState(name))
        self._stopProcess(name)

    def _stopProcess(self, name):
        self.assertRegistered(name)

        self._setProcessState(name, LifecycleState.STOPPING)

        proto = self.protocols.get(name, None)

        if proto is None:
            # If the proto isn't there then the process is definitely already
            # stopped.
            self._setProcessState(name, LifecycleState.STOPPED)
        else:
            # Same as procmon
            proc = proto.transport
            try:
                proc.signalProcess("TERM")
            except ProcessExitedAlready:
                pass
            else:
                self.murder[name] = self._reactor.callLater(
                    self.killTime, self._forceStopProcess, name, proc
                )

    def restartAll(self):
        """
        Manually restart all processes, regardless of how they've been configured.
        """
        for name in self._processes:
            self.restartProcess(name)

    def asdict(self):
        return dict(
            running=self.running,
            processes={name: self.getState(name) for name in self.states},
        )
Esempio n. 11
0
def main(ctx):
    log = create_logger(namespace="korbenware.cli.config")

    ctx.ensure_object(dict)

    ctx.obj["LOGGER"] = log
Esempio n. 12
0
class MimeRegistry:
    log = create_logger()

    def __init__(self, config, applications):
        self.environment = config.mime.environment
        self.applications = applications
        self.lookup = dict()
        self.defaults = dict()

        for filename in XDG_MIMEINFO_CACHE_FILES:
            self.log.debug("Loading desktop mimeinfo database {filename}",
                           filename=filename)
            database = DesktopDatabase.from_file(filename)

            if not database.parsed:
                self.log.warn(
                    "INI file parse error while loading mimeinfo database {filename} - skipping!",  # noqa
                    filename=filename,
                    log_failure=Failure(database.parse_exc),
                )
            else:
                for mimetype, apps in database.items():
                    self.log.debug(
                        "Associating applications {applications} with mimetype {mimetype}...",  # noqa
                        mimetype=mimetype,
                        applications=apps,
                    )
                    _insert(mimetype, apps, self.lookup)

        # TODO: Alternate algorithm that doesn't require reversing?
        # The list is short so it's not a big deal
        for mime_list in reversed(
                list(load_xdg_mime_lists(environment=self.environment))):
            if mime_list.parsed:
                added_associations = mime_list.get_added_associations()
                for mimetype, apps in added_associations.items():
                    self.log.debug(
                        "Associating applications {applications} with mimetype {mimetype}...",  # noqa
                        mimetype=mimetype,
                        applications=apps,
                    )
                    _insert(mimetype, apps, self.lookup)

                removed_associations = mime_list.get_removed_associations()
                for mimetype, apps in removed_associations.items():
                    self.log.debug(
                        "Disassociating applications {applications} from mimetype {mimetype}...",  # noqa
                        mimetype=mimetype,
                        applications=apps,
                    )
                    _remove(mimetype, apps, self.lookup)

                    # Assumption is that if the mimetype is removed that we
                    # don't want an associated default application either.
                    #
                    # I could change my mind on this.
                    if mimetype in self.defaults:
                        to_remove = {
                            app
                            for app in apps if app in self.defaults[mimetype]
                        }
                        if to_remove:
                            self.log.debug(
                                "Removing applications {applications} as defaults from mimetype {mimetype}...",  # noqa
                                mimetype=mimetype,
                                applications=list(to_remove),
                            )
                        for removed_app in to_remove:
                            self.defaults[mimetype] = [
                                app for app in self.defaults[mimetype]
                                if app is not removed_app
                            ]

                default_applications = mime_list.get_default_applications()

                for mimetype, apps in default_applications.items():
                    self.log.debug(
                        "Registering applications {applications} as the defaults for mimetype {mimetype}...",  # noqa
                        mimetype=mimetype,
                        applications=apps,
                    )
                    _insert(mimetype, apps, self.lookup)

                    # Current assumption is that an override should override
                    # the entire key.
                    #
                    # I could change my mind on this.
                    self.defaults[mimetype] = apps
            else:
                self.log.warn(
                    "Parse issues while loading mimeapp list at {filename} - skipping!",  # noqa
                    filename=mime_list.filename,
                    log_failure=Failure(mime_list.parse_exc),
                )

    def applications_by_filename(self, filename):
        return [
            self.applications.entries[key]
            for key in self.lookup[get_type(filename)]
            if key in self.applications.entries
        ]

    def default_by_filename(self, filename):
        return [
            self.applications.entries[key]
            for key in self.defaults[get_type(filename)]
            if key in self.applications.entries
        ]
Esempio n. 13
0
    try:
        f = open(filename, "r")
    except FileNotFoundError as e:
        raise NoConfigurationFoundError() from e

    with f:
        toml_config = toml.load(f)

    structured = cattr.structure(toml_config, BaseConfig)

    structured.meta.config_filename = filename

    return structured


log = create_logger()


def _log_config(path, obj, level):
    for attr_ in obj.__attrs_attrs__:
        if hasattr(attr_.type, "__attrs_attrs__"):
            _log_config(path + [attr_.name], getattr(obj, attr_.name), level)
        else:
            log.emit(
                level,
                "config: {path}={value}",
                path=".".join(path + [attr_.name]),
                value=getattr(obj, attr_.name),
            )

Esempio n. 14
0
File: ctl.py Progetto: disco0/public
def main(ctx):
    config = ctx.config

    log = create_logger(namespace="korbenware.cli.ctl")