Skip to content

Commit

Permalink
Merge pull request #1472 from Exirel/sopel-cli-utils
Browse files Browse the repository at this point in the history
sopel.cli.utils
  • Loading branch information
dgw committed Feb 28, 2019
2 parents af30862 + 2bf5e05 commit 460f9dd
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 141 deletions.
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::
[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
from sopel.tools import stderr

if sys.version_info < (2, 7):
Expand All @@ -30,6 +32,7 @@
Config,
_create_config,
ConfigurationError,
ConfigurationNotFound,
DEFAULT_HOMEDIR,
_wizard
)
Expand All @@ -45,82 +48,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 @@ -179,7 +111,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 @@ -196,23 +128,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)

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

return bot_config


Expand Down

0 comments on commit 460f9dd

Please sign in to comment.