Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -433,22 +433,35 @@ Aliases
~~~~~~~

The configargparse.ArgumentParser API inherits its class and method
names from argparse and also provides the following shorter names for
names from argparse and also provides the following shorter alias names for
convenience:

- p = configargparse.get_arg_parser() # get global singleton instance
- p = configargparse.get_parser()
- p = configargparse.ArgParser() # create a new instance
- p = configargparse.Parser()
- p.add_arg(..)
- p.add(..)
- options = p.parse(..)
**Class Aliases:**

HelpFormatters:
- ``ArgParser`` = ``ArgumentParser`` (create a new instance)
- ``Parser`` = ``ArgumentParser``

- RawFormatter = RawDescriptionHelpFormatter
- DefaultsFormatter = ArgumentDefaultsHelpFormatter
- DefaultsRawFormatter = ArgumentDefaultsRawHelpFormatter
**Method Aliases:**

- ``p.add(..)`` = ``p.add_argument(..)``
- ``p.add_arg(..)`` = ``p.add_argument(..)``
- ``p.parse(..)`` = ``p.parse_args(..)``
- ``p.parse_known(..)`` = ``p.parse_known_args(..)``

**Function Aliases (for global singleton):**

- ``configargparse.get_arg_parser()`` = ``configargparse.get_argument_parser()``
- ``configargparse.get_parser()`` = ``configargparse.get_argument_parser()``
- ``configargparse.getArgumentParser()`` = ``configargparse.get_argument_parser()``
- ``configargparse.getArgParser()`` = ``configargparse.get_argument_parser()``
- ``configargparse.getParser()`` = ``configargparse.get_argument_parser()``
- ``configargparse.initArgumentParser()`` = ``configargparse.init_argument_parser()``

**HelpFormatter Aliases:**

- ``RawFormatter`` = ``RawDescriptionHelpFormatter``
- ``DefaultsFormatter`` = ``ArgumentDefaultsHelpFormatter``
- ``DefaultsRawFormatter`` = ``ArgumentDefaultsRawHelpFormatter``

API Documentation
~~~~~~~~~~~~~~~~~
Expand Down
28 changes: 22 additions & 6 deletions configargparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,10 +968,18 @@ def parse_known_args(
value = [elem.strip() for elem in value[1:-1].split(",")]
env_var_args += self.convert_item_to_command_line_arg(action, key, value)

if nargs:
args = args + env_var_args
# Insert env var args before the first optional arg (starts with -)
# to preserve -- separator and positional args that come after it.
# If nargs is True, we still need to respect the -- separator.
insertion_index = 0
for i, arg in enumerate(args):
if arg.startswith(tuple(self.prefix_chars)):
insertion_index = i
break
else:
args = env_var_args + args
# No optional args found, append to end
insertion_index = len(args)
args = args[:insertion_index] + env_var_args + args[insertion_index:]

if env_var_args:
self._source_to_settings[_ENV_VAR_SOURCE_KEY] = OrderedDict(
Expand Down Expand Up @@ -1054,10 +1062,18 @@ def parse_known_args(
):
nargs = True

if nargs:
args = args + config_args
# Insert config args before the first optional arg (starts with -)
# to preserve -- separator and positional args that come after it.
# If nargs is True, we still need to respect the -- separator.
insertion_index = 0
for i, arg in enumerate(args):
if arg.startswith(tuple(self.prefix_chars)):
insertion_index = i
break
else:
args = config_args + args
# No optional args found, append to end
insertion_index = len(args)
args = args[:insertion_index] + config_args + args[insertion_index:]

# save default settings for use by print_values()
default_settings = OrderedDict()
Expand Down
50 changes: 50 additions & 0 deletions tests/test_configargparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,56 @@ def error_func(path):
args="-g file.txt",
)

def testDoubleDashSeparator(self):
"""Test that -- separator correctly separates optional from positional args
when config file or env vars are used. Regression test for issue #298."""
# Create a config file with list option
config_file = tempfile.NamedTemporaryFile(
mode="w", delete=False, suffix=".conf"
)
config_file.write("list = [1, 2, 3]\n")
config_file.flush()
config_file.close() # Close file to avoid Windows file locking issues

try:
# Test with config file
self.initParser(args_for_setting_config_path=["-c", "--config"])
self.parser.add_argument("--list", action="append")
self.parser.add_argument("positional_arg", nargs="*")

# Parse with -- separator
ns = self.parse(args=f"-c {config_file.name} -- foo bar")

# Positional args should only contain 'foo' and 'bar'
# NOT the config file values '1', '2', '3'
self.assertEqual(ns.positional_arg, ["foo", "bar"])
self.assertEqual(ns.list, ["1", "2", "3"])

# Test without -- separator (config file args should be inserted correctly)
ns = self.parse(args=f"-c {config_file.name} foo bar")
self.assertEqual(ns.positional_arg, ["foo", "bar"])
self.assertEqual(ns.list, ["1", "2", "3"])

# Test with env var and -- separator
self.initParser()
self.parser.add_argument("--list", action="append", env_var="MY_LIST")
self.parser.add_argument("positional_arg", nargs="*")

old_env = os.environ.get("MY_LIST")
try:
os.environ["MY_LIST"] = "[1,2,3]"
ns = self.parse(args="-- foo bar")
self.assertEqual(ns.positional_arg, ["foo", "bar"])
self.assertEqual(ns.list, ["1", "2", "3"])
finally:
if old_env is not None:
os.environ["MY_LIST"] = old_env
elif "MY_LIST" in os.environ:
del os.environ["MY_LIST"]

finally:
os.unlink(config_file.name)


class TestConfigFileParsers(TestCase):
"""Test ConfigFileParser subclasses in isolation"""
Expand Down