Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

6 changed files with 86 additions and 125 deletions

1
.gitignore vendored
View File

@ -138,4 +138,3 @@ dmypy.json
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
.vscode

View File

@ -29,7 +29,7 @@ args, kwargs = parse_args(argv=['pargv.py', '--name=pargv'])
# Specification # Specification
`parse_args` parses arguments in the following way, assuming the following command line arguments (`sys.argv`): `['/pargv/pargv.py', 'command', 'positional', '--flag', '--optional=value', 'test', '--output-file', 'filename', '-ab=one', 'two']` `parse_args` parses arguments in the following way, assuming the following command line arguments (`sys.argv`): `['/pargv/pargv.py', 'command', 'positional', '--flag', '--optional=value', 'test', '--output-file', 'filename', '-flg', 'name', 'name2']`
By calling `args, kwargs = parse_args()`, this would return the following list and dict: By calling `args, kwargs = parse_args()`, this would return the following list and dict:
@ -38,9 +38,10 @@ args = ['/pargv/pargv.py', 'command', 'positional']
kwargs = { kwargs = {
'flag': True, 'flag': True,
'optional': ['value', 'test'], 'optional': ['value', 'test'],
'output_file': 'filename', 'output_file': ['filename'],
'a': True, 'f': True,
'b': ['one', 'two'], 'l': True,
'g': ['name', 'name2'],
} }
``` ```
@ -48,8 +49,6 @@ kwargs = {
- All arguments before the first option (starting with a hyphen) are considered positional arguments (`args`) - All arguments before the first option (starting with a hyphen) are considered positional arguments (`args`)
- All other arguments are considered optional keyword arguments (`kwargs`) - All other arguments are considered optional keyword arguments (`kwargs`)
- Optional arguments without leading hyphens are considered as values to preceding keyword arguments - Optional arguments without leading hyphens are considered as values to preceding keyword arguments and saved as list
- One value of an option is stored as a string, multiple values are stored as a list - Flags are recorded with the value `True` in the dict
- Flags are stored with the value `True` in the dict - Up to 2 leading hyphens are stripped from options, all other hyphens are converted into underscores (`---test-this-` would become `_test_this_`)
- Up to 2 leading hyphens are stripped from options, all other hyphens are converted into underscores (`---test-this-` would become the key `_test_this_` in the dict)
- For multiple short options such as `-vao=file.ext` or `-vao file.ext`, the last option (`o`) is set to the specified value, while other options (`v` and `a`) are set to `True`.

View File

@ -1,9 +1,6 @@
import sys import sys
__version__ = '0.2.1'
def parse_args(argv=None): def parse_args(argv=None):
""" """
Returns a tuple of (args, kwargs) from a given list of command line arguments. Returns a tuple of (args, kwargs) from a given list of command line arguments.
@ -15,32 +12,30 @@ def parse_args(argv=None):
for arg in argv: for arg in argv:
if arg.startswith('-'): if arg.startswith('-'):
break break
args.append(arg.replace('-', '_')) args.append(arg)
argv = argv[len(args):]
kwargs = {} kwargs = {}
value = None values = []
for arg in argv[len(args):]: for arg in argv:
if not arg.startswith('-'): if not arg.startswith('-'):
if isinstance(kwargs[key], bool): values.append(arg)
kwargs[key] = arg else:
elif isinstance(kwargs[key], str): if len(values):
kwargs[key] = [kwargs[key]] + [arg] kwargs[key] = values
elif isinstance(kwargs[key], list): values = []
kwargs[key].append(arg)
continue
key = arg[:2].lstrip('-') + arg[2:].replace('-', '_') key = arg[:2].lstrip('-') + arg[2:].replace('-', '_')
if arg.startswith('--'):
if '=' in key: if '=' in arg:
key, value = key.split('=') key, value = key.split('=')
values.append(value)
if not arg.startswith('--'): kwargs[key] = values
for k in key[:-1]: else:
kwargs[key] = True
elif arg.startswith('-'):
for k in key:
kwargs[k] = True kwargs[k] = True
key = key[-1] key = arg[-1]
if len(values):
kwargs[key] = value if value else True kwargs[key] = values
value = None
return (args, kwargs) return (args, kwargs)

View File

@ -6,11 +6,10 @@ with open('README.md') as f:
setup( setup(
name='pargv', name='pargv',
version='0.2.1', version='0.1.0',
description='Parse command line arguments into a list of args and a dict of kwargs.', description='Parse command line arguments into a list of args and a dict of kwargs.',
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
py_modules=['pargv'],
extras_require={ extras_require={
'dev':[ 'dev':[
'pytest', 'pytest',

View File

@ -6,58 +6,87 @@ from pargv import parse_args
def test_no_argv(): def test_no_argv():
with mock.patch('sys.argv', []): with mock.patch('sys.argv', []):
assert parse_args() == ([], {}) args, kwargs = parse_args()
assert args == []
assert kwargs == {}
def test_use_sys_argv_by_default(): def test_use_sys_argv_by_default():
with mock.patch('sys.argv', ['argument', '--flag']): with mock.patch('sys.argv', ['app', 'command', '--option=one', 'two', '--flag', '-io']):
assert parse_args() == (['argument'], {'flag': True}) args, kwargs = parse_args()
assert args == ['app', 'command']
assert kwargs == {
'option': ['one', 'two'],
'flag': True,
'i': True,
'o': True
}
def test_single_positional_argument(): def test_single_positional_argument():
assert parse_args(['argument']) == (['argument'], {}) args, kwargs = parse_args(['app'])
assert args == ['app']
assert kwargs == {}
def test_single_positional_argument_with_underscore():
assert parse_args(['the-argument']) == (['the_argument'], {})
assert parse_args(['a-b']) == (['a_b'], {})
def test_optional_argument_with_underscore():
assert parse_args(['--a-b']) == ([], {'a_b': True})
def test_positional_arguments(): def test_positional_arguments():
assert parse_args(['argument', 'command']) == (['argument', 'command'], {}) args, kwargs = parse_args(['app', 'command'])
assert args == ['app', 'command']
assert kwargs == {}
def test_one_positional_and_optional_argument(): def test_one_positional_and_optional_argument():
assert parse_args(['argument', '--help']) == (['argument'], {'help': True}) args, kwargs = parse_args(['app', '--help'])
assert args == ['app']
assert kwargs == {'help': True}
def test_one_positional_and_optional_argument_with_values():
assert parse_args(['argument', '--amount=2', '3']) == (['argument'], {'amount': ['2', '3']})
def test_short_arg_with_single_option(): def test_positional_and_optional_arguments():
assert parse_args(['-a', 'b', '--abc', 'd']) == ([], {'a': 'b', 'abc': 'd'}) args, kwargs = parse_args(['app', 'command', '--inputfile', '--outputfile'])
assert args == ['app', 'command']
assert kwargs == {
'inputfile': True,
'outputfile': True
}
def test_short_arg_with_multiple_options(): def test_short_arg_with_multiple_options():
assert parse_args(['-i', 'a', 'b']) == ([], {'i': ['a', 'b']}) args, kwargs = parse_args(['-a', '-i', 'a', 'b'])
assert args == []
assert kwargs == {
'a': True,
'i': ['a', 'b']
}
def test_short_args_with_equals():
assert parse_args(['-ab=c']) == ([], {'a': True, 'b': 'c'})
assert parse_args(['-ab=c', 'd']) == ([], {'a': True, 'b': ['c', 'd']})
def test_long_args_with_equals(): def test_long_args_with_equals():
assert parse_args(['--input-file=a.py']) == ([], {'input_file': 'a.py'}) args, kwargs = parse_args(['--input-file=a.py', '--output-file=b.py'])
assert args == []
assert kwargs == {
'input_file': ['a.py'],
'output_file': ['b.py']
}
def test_unintended_hyphen(): def test_unintended_hyphen():
assert parse_args(['---triple-hyphen-']) == ([], {'_triple_hyphen_': True}) args, kwargs = parse_args(['---triple-hyphen-'])
assert args == []
assert kwargs == {'_triple_hyphen_': True}
@pytest.mark.parametrize('argv, args, kwargs', (( @pytest.mark.parametrize('argv, args, kwargs', ((
['/pargv/pargv.py', 'command', 'positional', '--flag', '--optional=value', 'test', '--output-file', 'filename', '-flg=name', 'name2'], ['/pargv/pargv.py', 'command', 'positional', '--flag', '--optional=value', 'test', '--output-file', 'filename', '-flg', 'name', 'name2'],
['/pargv/pargv.py', 'command', 'positional'], ['/pargv/pargv.py', 'command', 'positional'],
{ {
'flag': True, 'flag': True,
'optional': ['value', 'test'], 'optional': ['value', 'test'],
'output_file': 'filename', 'output_file': ['filename'],
'f': True, 'f': True,
'l': True, 'l': True,
'g': ['name', 'name2'], 'g': ['name', 'name2'],
} }
),)) ),))
def test_all_parameters_at_once(argv, args, kwargs): def test_all_parameters_at_once(argv, args, kwargs):
assert parse_args(argv) == (args, kwargs) _args, _kwargs = parse_args(argv)
assert _args == args
assert _kwargs == kwargs

View File

@ -1,60 +0,0 @@
from pargv import parse_args
import pytest
def test_io():
for (argv, args, kwargs) in (
# Arguments
('name', ['name'], {}),
('name command', ['name', 'command'], {}),
# Hyphens
('a-b', ['a_b'], {}),
('---a', [], {'_a': True}),
('-a-', [], {'a': True, '_': True}),
# Options
('--option', [], {'option': True}),
('--option=2', [], {'option': '2'}),
('--option=2 4', [], {'option': ['2', '4']}),
('--option 2', [], {'option': '2'}),
('--option 2 4', [], {'option': ['2', '4']}),
# Short options
('-o', [], {'o': True}),
('-o=2', [], {'o': '2'}),
('-o=2 4', [], {'o': ['2', '4']}),
('-o 2', [], {'o': '2'}),
('-o 2 4', [], {'o': ['2', '4']}),
# Multiple short options
('-ox', [], {'o': True, 'x': True}),
('-ox=2', [], {'o': True, 'x': '2'}),
('-ox=2 4', [], {'o': True, 'x': ['2', '4']}),
('-ox 2', [], {'o': True, 'x': '2'}),
('-ox 2 4', [], {'o': True, 'x': ['2', '4']}),
):
assert parse_args(argv.split()) == (args, kwargs)
def test_empty_input():
for argv in (
'',
[],
{},
None,
False,
):
args, kwargs = parse_args(argv)
assert (len(args), len(kwargs)) == (1, 0)
def test_type_errors():
for argv in (
1,
True,
{'a': 1},
):
with pytest.raises(TypeError):
parse_args(argv)
def test_attribute_errors():
for argv in (
[1],
):
with pytest.raises(AttributeError):
parse_args(argv)