Skip to content

Commit d3b9a93

Browse files
committed
cli: fix SOPEL_CONFIG env var broken usage
1 parent f455541 commit d3b9a93

File tree

4 files changed

+146
-58
lines changed

4 files changed

+146
-58
lines changed

sopel/cli/run.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -356,22 +356,24 @@ def get_configuration(options):
356356
return settings
357357

358358

359-
def get_pid_filename(options, pid_dir):
360-
"""Get the pid file name in ``pid_dir`` from the given ``options``.
359+
def get_pid_filename(settings, pid_dir):
360+
"""Get the pid file name in ``pid_dir`` from the given ``settings``.
361361
362-
:param options: command line options
362+
:param settings: Sopel config
363+
:type settings: :class:`sopel.config.Config`
363364
:param str pid_dir: path to the pid directory
364365
:return: absolute filename of the pid file
365366
366-
By default, it's ``sopel.pid``, but if a configuration filename is given
367-
in the ``options``, its basename is used to generate the filename, as:
367+
By default, it's ``sopel.pid``, but if the configuration's basename is not
368+
``default`` then it will be used to generate the pid file name as
368369
``sopel-{basename}.pid`` instead.
369370
"""
370371
name = 'sopel.pid'
371-
if options.config and options.config != 'default':
372-
basename = os.path.basename(options.config)
373-
if basename.endswith('.cfg'):
374-
basename = basename[:-4]
372+
if settings.basename != 'default':
373+
filename = os.path.basename(settings.filename)
374+
basename, ext = os.path.splitext(filename)
375+
if ext != '.cfg':
376+
basename = filename
375377
name = 'sopel-%s.pid' % basename
376378

377379
return os.path.abspath(os.path.join(pid_dir, name))
@@ -402,18 +404,18 @@ def command_start(opts):
402404
"""Start a Sopel instance"""
403405
# Step One: Get the configuration file and prepare to run
404406
try:
405-
config_module = get_configuration(opts)
407+
settings = get_configuration(opts)
406408
except config.ConfigurationError as e:
407409
tools.stderr(e)
408410
return ERR_CODE_NO_RESTART
409411

410-
if config_module.core.not_configured:
412+
if settings.core.not_configured:
411413
tools.stderr('Bot is not configured, can\'t start')
412414
return ERR_CODE_NO_RESTART
413415

414416
# Step Two: Handle process-lifecycle options and manage the PID file
415-
pid_dir = config_module.core.pid_dir
416-
pid_file_path = get_pid_filename(opts, pid_dir)
417+
pid_dir = settings.core.pid_dir
418+
pid_file_path = get_pid_filename(settings, pid_dir)
417419
pid = get_running_pid(pid_file_path)
418420

419421
if pid is not None and tools.check_pid(pid):
@@ -432,7 +434,7 @@ def command_start(opts):
432434
pid_file.write(str(os.getpid()))
433435

434436
# Step Three: Run Sopel
435-
ret = run(config_module, pid_file_path)
437+
ret = run(settings, pid_file_path)
436438

437439
# Step Four: Shutdown Clean-Up
438440
os.unlink(pid_file_path)
@@ -471,7 +473,7 @@ def command_stop(opts):
471473
logger.setup_logging(settings)
472474

473475
# Get Sopel's PID
474-
filename = get_pid_filename(opts, settings.core.pid_dir)
476+
filename = get_pid_filename(settings, settings.core.pid_dir)
475477
pid = get_running_pid(filename)
476478

477479
if pid is None or not tools.check_pid(pid):
@@ -510,7 +512,7 @@ def command_restart(opts):
510512
logger.setup_logging(settings)
511513

512514
# Get Sopel's PID
513-
filename = get_pid_filename(opts, settings.core.pid_dir)
515+
filename = get_pid_filename(settings, settings.core.pid_dir)
514516
pid = get_running_pid(filename)
515517

516518
if pid is None or not tools.check_pid(pid):
@@ -585,18 +587,18 @@ def command_legacy(opts):
585587

586588
# Step Two: Get the configuration file and prepare to run
587589
try:
588-
config_module = get_configuration(opts)
590+
settings = get_configuration(opts)
589591
except config.ConfigurationError as e:
590592
tools.stderr(e)
591593
return ERR_CODE_NO_RESTART
592594

593-
if config_module.core.not_configured:
595+
if settings.core.not_configured:
594596
tools.stderr('Bot is not configured, can\'t start')
595597
return ERR_CODE_NO_RESTART
596598

597599
# Step Three: Handle process-lifecycle options and manage the PID file
598-
pid_dir = config_module.core.pid_dir
599-
pid_file_path = get_pid_filename(opts, pid_dir)
600+
pid_dir = settings.core.pid_dir
601+
pid_file_path = get_pid_filename(settings, pid_dir)
600602
old_pid = get_running_pid(pid_file_path)
601603

602604
if old_pid is not None and tools.check_pid(old_pid):
@@ -649,7 +651,7 @@ def command_legacy(opts):
649651
pid_file.write(str(os.getpid()))
650652

651653
# Step Four: Initialize and run Sopel
652-
ret = run(config_module, pid_file_path)
654+
ret = run(settings, pid_file_path)
653655
os.unlink(pid_file_path)
654656
if ret == -1:
655657
os.execv(sys.executable, ['python'] + sys.argv)

sopel/cli/utils.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ def add_common_arguments(parser):
266266
``config`` and ``configdir`` options that can be used to find and load
267267
Sopel's settings.
268268
269+
The default value for ``config`` is either the value of the environment
270+
variable ``SOPEL_CONFIG``, or the string ``default``.
271+
269272
.. seealso::
270273
271274
The :func:`sopel.cli.utils.load_settings` function uses an ``options``
@@ -274,15 +277,17 @@ def add_common_arguments(parser):
274277
"""
275278
parser.add_argument(
276279
'-c', '--config',
277-
default='default',
280+
default=os.environ.get('SOPEL_CONFIG') or 'default',
278281
metavar='filename',
279282
dest='config',
280283
help=inspect.cleandoc("""
281284
Use a specific configuration file.
282285
A config name can be given and the configuration file will be
283-
found in Sopel\'s homedir (defaults to ``~/.sopel/default.cfg``).
286+
found in Sopel's homedir (defaults to ``~/.sopel/default.cfg``).
284287
An absolute pathname can be provided instead to use an
285288
arbitrary location.
289+
When the ``SOPEL_CONFIG`` environment variable is set and not
290+
empty, it is used as the default value.
286291
"""))
287292
parser.add_argument(
288293
'--config-dir',
@@ -302,14 +307,14 @@ def load_settings(options):
302307
:raise sopel.config.ConfigurationError: raised when configuration is
303308
invalid
304309
305-
This function loads Sopel's settings from one of these sources:
310+
This function loads Sopel's settings from ``options.config``. This option's
311+
value should be from one of these sources:
306312
307-
* value of ``options.config``, if given,
308-
* ``SOPEL_CONFIG`` environment variable, if no option is given,
309-
* otherwise the ``default`` configuration is loaded,
313+
* given by the command line argument,
314+
* ``SOPEL_CONFIG`` environment variable, if the argument is not used,
315+
* otherwise it should default to ``default``,
310316
311-
then loads the settings and returns it as a :class:`~sopel.config.Config`
312-
object.
317+
then it returns it as a :class:`~sopel.config.Config` object.
313318
314319
If the configuration file can not be found, a
315320
:exc:`sopel.config.ConfigurationNotFound` error will be raised.
@@ -320,17 +325,11 @@ def load_settings(options):
320325
``config`` and ``configdir``.
321326
322327
The :func:`sopel.cli.utils.add_common_arguments` function should be
323-
used to add these options to the argument parser.
328+
used to add these options to the argument parser. This function is also
329+
responsible for using the environment variable or the default value.
324330
325331
"""
326-
# Default if no options.config or no env var or if they are empty
327-
name = 'default'
328-
if options.config:
329-
name = options.config
330-
elif 'SOPEL_CONFIG' in os.environ:
331-
name = os.environ['SOPEL_CONFIG'] or name # use default if empty
332-
333-
filename = find_config(options.configdir, name)
332+
filename = find_config(options.configdir, options.config)
334333

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

test/cli/test_cli_run.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
)
1818

1919

20+
TMP_CONFIG = """
21+
[core]
22+
owner = testnick
23+
nick = TestBot
24+
enable = coretasks
25+
"""
26+
27+
2028
@contextmanager
2129
def cd(newdir):
2230
prevdir = os.getcwd()
@@ -366,41 +374,30 @@ def test_get_configuration(tmpdir):
366374
assert result.core.owner == 'TestName'
367375

368376

369-
def test_get_pid_filename_default():
377+
def test_get_pid_filename_default(configfactory):
370378
"""Assert function returns the default filename from given ``pid_dir``"""
371379
pid_dir = '/pid'
372-
parser = build_parser()
373-
options = parser.parse_args(['legacy'])
380+
settings = configfactory('default.cfg', TMP_CONFIG)
374381

375-
result = get_pid_filename(options, pid_dir)
382+
result = get_pid_filename(settings, pid_dir)
376383
assert result == pid_dir + '/sopel.pid'
377384

378385

379-
def test_get_pid_filename_named():
386+
def test_get_pid_filename_named(configfactory):
380387
"""Assert function returns a specific filename when config (with extension) is set"""
381388
pid_dir = '/pid'
382-
parser = build_parser()
389+
settings = configfactory('test.cfg', TMP_CONFIG)
383390

384-
# With extension
385-
options = parser.parse_args(['legacy', '-c', 'test.cfg'])
386-
387-
result = get_pid_filename(options, pid_dir)
391+
result = get_pid_filename(settings, pid_dir)
388392
assert result == pid_dir + '/sopel-test.pid'
389393

390-
# Without extension
391-
options = parser.parse_args(['legacy', '-c', 'test'])
392-
393-
result = get_pid_filename(options, pid_dir)
394-
assert result == pid_dir + '/sopel-test.pid'
395394

396-
397-
def test_get_pid_filename_ext_not_cfg():
395+
def test_get_pid_filename_ext_not_cfg(configfactory):
398396
"""Assert function keeps the config file extension when it is not cfg"""
399397
pid_dir = '/pid'
400-
parser = build_parser()
401-
options = parser.parse_args(['legacy', '-c', 'test.ini'])
398+
settings = configfactory('test.ini', TMP_CONFIG)
402399

403-
result = get_pid_filename(options, pid_dir)
400+
result = get_pid_filename(settings, pid_dir)
404401
assert result == pid_dir + '/sopel-test.ini.pid'
405402

406403

test/cli/test_cli_utils.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@
1515
find_config,
1616
get_many_text,
1717
green,
18+
load_settings,
1819
red,
1920
yellow,
2021
)
2122

2223

24+
TMP_CONFIG = """
25+
[core]
26+
owner = testnick
27+
nick = TestBot
28+
enable = coretasks
29+
"""
30+
31+
2332
@contextmanager
2433
def cd(newdir):
2534
prevdir = os.getcwd()
@@ -206,3 +215,84 @@ def test_get_many_text(items, expected):
206215
'elements {first} and {second}',
207216
'elements {left}, and {last}')
208217
assert result == expected
218+
219+
220+
def test_load_settings(config_dir):
221+
config_dir.join('config.cfg').write(TMP_CONFIG)
222+
parser = argparse.ArgumentParser()
223+
add_common_arguments(parser)
224+
225+
options = parser.parse_args([
226+
'--config-dir', config_dir.strpath,
227+
'-c', 'config.cfg',
228+
])
229+
230+
settings = load_settings(options)
231+
assert isinstance(settings, config.Config)
232+
assert settings.basename == 'config'
233+
234+
235+
def test_load_settings_arg_priority_over_env(monkeypatch, config_dir):
236+
monkeypatch.setenv('SOPEL_CONFIG', 'fromenv')
237+
238+
config_dir.join('fromenv.cfg').write(TMP_CONFIG)
239+
config_dir.join('fromarg.cfg').write(TMP_CONFIG)
240+
parser = argparse.ArgumentParser()
241+
add_common_arguments(parser)
242+
243+
options = parser.parse_args([
244+
'--config-dir', config_dir.strpath,
245+
'-c', 'fromarg',
246+
])
247+
248+
settings = load_settings(options)
249+
assert isinstance(settings, config.Config)
250+
assert settings.basename == 'fromarg'
251+
252+
253+
def test_load_settings_default(config_dir):
254+
config_dir.join('default.cfg').write(TMP_CONFIG)
255+
parser = argparse.ArgumentParser()
256+
add_common_arguments(parser)
257+
258+
options = parser.parse_args(['--config-dir', config_dir.strpath])
259+
260+
settings = load_settings(options)
261+
assert isinstance(settings, config.Config)
262+
263+
264+
def test_load_settings_default_env_var(monkeypatch, config_dir):
265+
monkeypatch.setenv('SOPEL_CONFIG', 'config')
266+
267+
config_dir.join('config.cfg').write(TMP_CONFIG)
268+
parser = argparse.ArgumentParser()
269+
add_common_arguments(parser)
270+
271+
options = parser.parse_args(['--config-dir', config_dir.strpath])
272+
273+
settings = load_settings(options)
274+
assert isinstance(settings, config.Config)
275+
assert settings.basename == 'config'
276+
277+
278+
def test_load_settings_default_not_found(config_dir):
279+
parser = argparse.ArgumentParser()
280+
add_common_arguments(parser)
281+
282+
options = parser.parse_args(['--config-dir', config_dir.strpath])
283+
284+
with pytest.raises(config.ConfigurationNotFound):
285+
load_settings(options)
286+
287+
288+
def test_load_settings_invalid(config_dir):
289+
parser = argparse.ArgumentParser()
290+
add_common_arguments(parser)
291+
292+
options = parser.parse_args([
293+
'--config-dir', config_dir.strpath,
294+
'-c', 'config.cfg',
295+
])
296+
297+
with pytest.raises(ValueError): # no [core] section
298+
load_settings(options)

0 commit comments

Comments
 (0)