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

sopel.cli.utils #1472

Merged
merged 8 commits into from Feb 28, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -66,7 +66,7 @@ def read_reqs(path):
# Distutils is shit, and doesn't check if it's a list of basestring
# but instead requires str.
packages=[str('sopel'), str('sopel.modules'),
str('sopel.config'), str('sopel.tools')],
str('sopel.cli'), str('sopel.config'), str('sopel.tools')],
classifiers=classifiers,
license='Eiffel Forum License, version 2',
platforms='Linux x86, x86-64',
Expand Down
10 changes: 10 additions & 0 deletions sopel/cli/__init__.py
@@ -0,0 +1,10 @@
# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function, division

# Shortcut imports
from .utils import ( # noqa
add_common_arguments,
enumerate_configs,
find_config,
load_settings
)
155 changes: 155 additions & 0 deletions sopel/cli/utils.py
@@ -0,0 +1,155 @@
# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function, division

import os

from sopel import config

# Allow clean import *
__all__ = [
'enumerate_configs',
'find_config',
'add_common_arguments',
'load_settings',
]


def enumerate_configs(config_dir, extension='.cfg'):
"""List configuration files from ``config_dir`` with ``extension``

:param str config_dir: path to the configuration directory
:param str extension: configuration file's extension (default to ``.cfg``)
:return: a list of configuration filenames found in ``config_dir`` with
the correct ``extension``
:rtype: list

Example::

>>> from sopel import cli, config
>>> os.listdir(config.DEFAULT_HOMEDIR)
['config.cfg', 'extra.ini', 'module.cfg', 'README']
>>> cli.enumerate_configs(config.DEFAULT_HOMEDIR)
['config.cfg', 'module.cfg']
>>> cli.enumerate_configs(config.DEFAULT_HOMEDIR, '.ini')
['extra.ini']

"""
if not os.path.isdir(config_dir):
return

for item in os.listdir(config_dir):
if item.endswith(extension):
yield item


def find_config(config_dir, name, extension='.cfg'):
"""Build the absolute path for the given configuration file ``name``

:param str config_dir: path to the configuration directory
:param str name: configuration file ``name``
:param str extension: configuration file's extension (default to ``.cfg``)
:return: the path of the configuration file, either in the current
directory or from the ``config_dir`` directory

This function tries different locations:

* the current directory
* the ``config_dir`` directory with the ``extension`` suffix
* the ``config_dir`` directory without a suffix

Example::

>>> from sopel import run_script
>>> os.listdir()
['local.cfg', 'extra.ini']
>>> os.listdir(config.DEFAULT_HOMEDIR)
['config.cfg', 'extra.ini', 'module.cfg', 'README']
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'local.cfg')
'local.cfg'
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'local')
'/home/username/.sopel/local'
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'config')
'/home/username/.sopel/config.cfg'
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'extra', '.ini')
'/home/username/.sopel/extra.ini'

"""
if os.path.isfile(name):
return name
name_ext = name + extension
for filename in enumerate_configs(config_dir, extension):
if name_ext == filename:
return os.path.join(config_dir, name_ext)

return os.path.join(config_dir, name)


def add_common_arguments(parser):
"""Add common and configuration-related arguments to a ``parser``.

:param parser: Argument parser (or subparser)
:type parser: argparse.ArgumentParser

This functions adds the common arguments for Sopel's command line tools.
At the moment, this functions adds only one argument to the parser: the
argument used as the standard way to define a configuration filename.

This can be used on an argument parser, or an argument subparser, to handle
these cases::
Exirel marked this conversation as resolved.
Show resolved Hide resolved

[sopel-command] -c [filename]
[sopel-command] [action] -c [filename]

Then, when the parser parses the command line arguments, it will expose
a ``config`` option to be used to find and load Sopel's settings.

.. seealso::

The :func:`sopel.cli.utils.load_settings` function uses an ``options``
object from a parser configured with such arguments.

"""
parser.add_argument(
'-c', '--config',
default=None,
metavar='filename',
dest='config',
help='Use a specific configuration file')


def load_settings(options):
"""Load Sopel's settings using the command line's ``options``.

:param options: parsed arguments
:return: sopel configuration
:rtype: :class:`sopel.config.Config`
:raise sopel.config.ConfigurationNotFound: raised when configuration file
is not found
:raise sopel.config.ConfigurationError: raised when configuration is
invalid

This function loads Sopel's settings from one of these sources:

* value of ``options.config``, if given,
* or the ``default`` configuration is loaded,

then loads the settings and returns it as a :class:`~sopel.config.Config`
object.

If the configuration file can not be found, a
:exc:`sopel.config.ConfigurationNotFound` error will be raised.

.. note::

To use this function effectively, the
:func:`sopel.cli.utils.add_common_arguments` function should be used to
add the proper option to the argument parser.

"""
name = options.config or 'default'
filename = find_config(config.DEFAULT_HOMEDIR, name)

if not os.path.isfile(filename):
raise config.ConfigurationNotFound(filename=filename)

return config.Config(filename)
13 changes: 11 additions & 2 deletions sopel/config/__init__.py
Expand Up @@ -38,15 +38,24 @@


class ConfigurationError(Exception):
""" Exception type for configuration errors """

"""Exception type for configuration errors"""
def __init__(self, value):
self.value = value

def __str__(self):
return 'ConfigurationError: %s' % self.value


class ConfigurationNotFound(ConfigurationError):
"""Configuration file not found."""
def __init__(self, filename):
self.filename = filename
"""Path to the configuration file not found"""

def __str__(self):
return 'Unable to find the configuration file %s' % self.filename


class Config(object):
def __init__(self, filename, validate=True):
"""The bot's configuration.
Expand Down
90 changes: 11 additions & 79 deletions sopel/run_script.py
Expand Up @@ -11,6 +11,8 @@
from __future__ import unicode_literals, absolute_import, print_function, division

import sys

from sopel.cli import utils
dgw marked this conversation as resolved.
Show resolved Hide resolved
from sopel.tools import stderr

if sys.version_info < (2, 7):
Expand All @@ -29,6 +31,7 @@
Config,
_create_config,
ConfigurationError,
ConfigurationNotFound,
DEFAULT_HOMEDIR,
_wizard
)
Expand All @@ -44,82 +47,11 @@
"""


def enumerate_configs(config_dir, extension='.cfg'):
"""List configuration file from ``config_dir`` with ``extension``

:param str config_dir: path to the configuration directory
:param str extension: configuration file's extension (default to ``.cfg``)
:return: a list of configuration filename found in ``config_dir`` with
the correct ``extension``
:rtype: list

Example::

>>> from sopel import run_script, config
>>> os.listdir(config.DEFAULT_HOMEDIR)
['config.cfg', 'extra.ini', 'module.cfg', 'README']
>>> run_script.enumerate_configs(config.DEFAULT_HOMEDIR)
['config.cfg', 'module.cfg']
>>> run_script.enumerate_configs(config.DEFAULT_HOMEDIR, '.ini')
['extra.ini']

"""
if not os.path.isdir(config_dir):
return

for item in os.listdir(config_dir):
if item.endswith(extension):
yield item


def find_config(config_dir, name, extension='.cfg'):
"""Build the absolute path for the given configuration file ``name``

:param str config_dir: path to the configuration directory
:param str name: configuration file ``name``
:param str extension: configuration file's extension (default to ``.cfg``)
:return: the path of the configuration file, either in the current
directory or from the ``config_dir`` directory

This function tries different locations:

* the current directory
* the ``config_dir`` directory with the ``extension`` suffix
* the ``config_dir`` directory without a suffix

Example::

>>> from sopel import run_script
>>> os.listdir()
['local.cfg', 'extra.ini']
>>> os.listdir(config.DEFAULT_HOMEDIR)
['config.cfg', 'extra.ini', 'module.cfg', 'README']
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'local.cfg')
'local.cfg'
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'local')
'/home/username/.sopel/local'
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'config')
'/home/username/.sopel/config.cfg'
>>> run_script.find_config(config.DEFAULT_HOMEDIR, 'extra', '.ini')
'/home/username/.sopel/extra.ini'

"""
if os.path.isfile(name):
return name
name_ext = name + extension
for config in enumerate_configs(config_dir, extension):
if name_ext == config:
return os.path.join(config_dir, name_ext)

return os.path.join(config_dir, name)


def build_parser():
"""Build an ``argparse.ArgumentParser`` for the bot"""
parser = argparse.ArgumentParser(description='Sopel IRC Bot',
usage='%(prog)s [options]')
parser.add_argument('-c', '--config', metavar='filename',
help='use a specific configuration file')
utils.add_common_arguments(parser)
parser.add_argument("-d", '--fork', action="store_true",
dest="daemonize", help="Daemonize Sopel")
parser.add_argument("-q", '--quit', action="store_true", dest="quit",
Expand Down Expand Up @@ -174,7 +106,7 @@ def print_version():

def print_config():
"""Print list of available configurations from default homedir."""
configs = enumerate_configs(DEFAULT_HOMEDIR)
configs = utils.enumerate_configs(DEFAULT_HOMEDIR)
print('Config files in %s:' % DEFAULT_HOMEDIR)
config = None
for config in configs:
Expand All @@ -191,23 +123,23 @@ def get_configuration(options):
This may raise a ``sopel.config.ConfigurationError`` if the file is an
invalid configuration file.
"""
config_name = options.config or 'default'
config_path = find_config(DEFAULT_HOMEDIR, config_name)

if not os.path.isfile(config_path):
try:
bot_config = utils.load_settings(options)
except ConfigurationNotFound as error:
print(
"Welcome to Sopel!\n"
"I can't seem to find the configuration file, "
"so let's generate it!\n")

config_path = error.filename
if not config_path.endswith('.cfg'):
config_path = config_path + '.cfg'

config_path = _create_config(config_path)
# try to reload it now that it's created
bot_config = Config(config_path)
dgw marked this conversation as resolved.
Show resolved Hide resolved

bot_config = Config(config_path)
bot_config._is_daemonized = options.daemonize

return bot_config


Expand Down