345 lines
9.4 KiB
Python
345 lines
9.4 KiB
Python
|
|
#!/usr/bin/env python
|
||
|
|
|
||
|
|
import enum, pathlib
|
||
|
|
import itertools
|
||
|
|
import os
|
||
|
|
import re
|
||
|
|
import sys
|
||
|
|
from dataclasses import KW_ONLY, dataclass, field
|
||
|
|
from functools import partial, partialmethod
|
||
|
|
from operator import itemgetter
|
||
|
|
from time import sleep
|
||
|
|
|
||
|
|
import defl
|
||
|
|
from defl import CLIError, Null, Path, Undefined, cl, log, Assert, ArgTypeHint
|
||
|
|
from defl._argsFromObject_ import (_DefaultParserNotSpecified, _MalformedArguments)
|
||
|
|
from defl._typing_ import *
|
||
|
|
from defl.testing_ import Test, Tester, TestState
|
||
|
|
|
||
|
|
tester = Tester(name=__file__)
|
||
|
|
# TODO test choices
|
||
|
|
|
||
|
|
@dataclass(slots=True, kw_only=True, frozen=False)
|
||
|
|
class CLI:
|
||
|
|
def main(_, bb: str = 'cat'):
|
||
|
|
return locals()
|
||
|
|
|
||
|
|
def cli1(_, *extra):
|
||
|
|
return locals()
|
||
|
|
|
||
|
|
def cli2(
|
||
|
|
_, aa: bool, bb: str = 'cat', /, cc: bool = True, notTyped=1, dd: list[str] | None = None, *extra
|
||
|
|
):
|
||
|
|
return locals()
|
||
|
|
|
||
|
|
def cli3(_, aa: str, *bb, cc: str = 'default', dd: bool = False) -> str:
|
||
|
|
return locals()
|
||
|
|
|
||
|
|
def excludeMe(_):
|
||
|
|
...
|
||
|
|
|
||
|
|
def listInput(_, aa: list[str], bb: str, cc: str, *extra):
|
||
|
|
return locals()
|
||
|
|
|
||
|
|
mapSubParsersDefault = {'c1': 'cli1', 'c2': 'cli2', '-': 'main'}
|
||
|
|
|
||
|
|
def newAFO(parseIn, mapSubParsers=True):
|
||
|
|
return defl.cliAutoParse(
|
||
|
|
CLI,
|
||
|
|
exclude=['excludeMe'],
|
||
|
|
parseIn=parseIn,
|
||
|
|
runNow=false,
|
||
|
|
autoExit=False,
|
||
|
|
mapSubParsers=mapSubParsersDefault if mapSubParsers else {},
|
||
|
|
)
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def retTrue():
|
||
|
|
try:
|
||
|
|
afo = newAFO(['script.py'], mapSubParsers=False)
|
||
|
|
assert false, f'should not be reached'
|
||
|
|
except _DefaultParserNotSpecified:
|
||
|
|
pass
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def retTrue():
|
||
|
|
try:
|
||
|
|
afo = newAFO(['script.py', 'does not exist', 'extra'])
|
||
|
|
assert false, f'should not be reached'
|
||
|
|
except _DefaultParserNotSpecified:
|
||
|
|
pass
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def retTrue():
|
||
|
|
try:
|
||
|
|
afo = newAFO(['script.py', 'cli3', 'extra', 'extra'])
|
||
|
|
assert false, f'should not be reached'
|
||
|
|
except _MalformedArguments:
|
||
|
|
pass
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def retTrue():
|
||
|
|
afo = newAFO(['script.py', 'cli3', '-a', 'test'])
|
||
|
|
|
||
|
|
res = afo._parser._subparsers._group_actions[0].choices
|
||
|
|
a = res['cli3']._option_string_actions
|
||
|
|
|
||
|
|
assert type(a['-a']).__name__ == '_StoreAction'
|
||
|
|
assert type(a['-c']).__name__ == '_StoreAction'
|
||
|
|
assert type(a['-d']).__name__ == '_StoreTrueAction'
|
||
|
|
|
||
|
|
assert a['-a'].dest == 'aa'
|
||
|
|
assert a['-c'].dest == 'cc'
|
||
|
|
assert a['-d'].dest == 'dd'
|
||
|
|
|
||
|
|
assert a['-a'].nargs == None
|
||
|
|
assert a['-c'].nargs == None
|
||
|
|
assert a['-d'].nargs == 0
|
||
|
|
|
||
|
|
assert a['-a'].const is None
|
||
|
|
assert a['-c'].const is None
|
||
|
|
assert a['-d'].const is True
|
||
|
|
|
||
|
|
assert a['-a'].default is None
|
||
|
|
assert a['-c'].default == 'default'
|
||
|
|
assert a['-d'].default is False
|
||
|
|
|
||
|
|
assert a['-a'].type is str
|
||
|
|
assert a['-c'].type is str
|
||
|
|
assert a['-d'].type is None
|
||
|
|
|
||
|
|
assert a['-a'].choices is None
|
||
|
|
assert a['-c'].choices is None
|
||
|
|
assert a['-d'].choices is None
|
||
|
|
|
||
|
|
assert a['-a'].required is True
|
||
|
|
assert a['-c'].required is False
|
||
|
|
assert a['-d'].required is False
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def retTrue():
|
||
|
|
afo = newAFO(['script.py', 'cli2', 'test', '-d', '1', '-d', '2', '-b', 'dog', 'e1', 'e2', 'e3'])
|
||
|
|
|
||
|
|
# subParseFunc = [x['func'] for x in theD['_subParserList']]
|
||
|
|
# assert subParseFunc == ['cli1', 'cli2', 'main', '__root__']
|
||
|
|
assert isinstance(afo.theObj, CLI)
|
||
|
|
|
||
|
|
par = afo._parser
|
||
|
|
assert par.prog == 'test_argparse.py'
|
||
|
|
res = afo._parser._subparsers._group_actions[0].choices
|
||
|
|
assert set(res.keys()) == set(['cli1', 'c1', 'cli2', 'c2', 'cli3', 'main', 'listInput', '-'])
|
||
|
|
# for k, v in res.items():
|
||
|
|
# print(k, '\t', v)
|
||
|
|
|
||
|
|
root = afo._parser._option_string_actions
|
||
|
|
|
||
|
|
assert root['-z'].option_strings == ['-z', '--log']
|
||
|
|
assert type(root['-z']).__name__ == '_StoreAction'
|
||
|
|
assert root['-z'].dest == 'log'
|
||
|
|
assert root['-z'].nargs is None
|
||
|
|
assert root['-z'].const is None
|
||
|
|
assert root['-z'].const is None
|
||
|
|
assert root['-z'].default == 'info'
|
||
|
|
assert root['-z'].type == str
|
||
|
|
assert root['-z'].choices is None
|
||
|
|
assert root['-z'].required is False
|
||
|
|
|
||
|
|
# == cli1 arg
|
||
|
|
|
||
|
|
a = res['cli2']._option_string_actions
|
||
|
|
|
||
|
|
assert a['-a'].option_strings == ['-a', '--aa']
|
||
|
|
assert a['--aa'].option_strings == ['-a', '--aa']
|
||
|
|
assert a['-b'].option_strings == ['-b', '--bb']
|
||
|
|
assert a['--bb'].option_strings == ['-b', '--bb']
|
||
|
|
assert a['-c'].option_strings == ['-c', '--cc']
|
||
|
|
assert a['--cc'].option_strings == ['-c', '--cc']
|
||
|
|
assert a['-d'].option_strings == ['-d', '--dd']
|
||
|
|
assert a['--dd'].option_strings == ['-d', '--dd']
|
||
|
|
|
||
|
|
assert type(a['-a']).__name__ == '_StoreTrueAction', type(a['-a']).__name__
|
||
|
|
assert type(a['-b']).__name__ == '_StoreAction'
|
||
|
|
assert type(a['-c']).__name__ == '_StoreFalseAction'
|
||
|
|
assert type(a['-d']).__name__ == '_AppendAction'
|
||
|
|
|
||
|
|
assert a['-a'].dest == 'aa'
|
||
|
|
assert a['-b'].dest == 'bb'
|
||
|
|
assert a['-c'].dest == 'cc'
|
||
|
|
assert a['-d'].dest == 'dd'
|
||
|
|
|
||
|
|
assert a['-a'].nargs == 0, a['-a'].nargs
|
||
|
|
assert a['-b'].nargs is None
|
||
|
|
assert a['-c'].nargs == 0
|
||
|
|
assert a['-d'].nargs == 1
|
||
|
|
|
||
|
|
assert a['-a'].const is True, a['-a'].const
|
||
|
|
assert a['-b'].const is None
|
||
|
|
assert a['-c'].const is False
|
||
|
|
assert a['-d'].const is None
|
||
|
|
|
||
|
|
assert a['-a'].default is False
|
||
|
|
assert a['-b'].default == 'cat', a['-b'].default
|
||
|
|
assert a['-c'].default is True
|
||
|
|
assert a['-d'].default is None
|
||
|
|
|
||
|
|
assert a['-a'].type is None
|
||
|
|
assert a['-b'].type == str
|
||
|
|
assert a['-c'].type is None
|
||
|
|
assert a['-d'].type is None
|
||
|
|
|
||
|
|
assert a['-a'].choices is None
|
||
|
|
assert a['-b'].choices is None
|
||
|
|
assert a['-c'].choices is None
|
||
|
|
assert a['-d'].choices is None
|
||
|
|
|
||
|
|
assert a['-a'].required is False
|
||
|
|
assert a['-b'].required is False
|
||
|
|
assert a['-c'].required is False
|
||
|
|
assert a['-d'].required is False
|
||
|
|
|
||
|
|
res = afo.run()
|
||
|
|
|
||
|
|
res['aa'] == False,
|
||
|
|
res['bb'] == 'dog',
|
||
|
|
res['cc'] == True,
|
||
|
|
res['notTyped'] == 1,
|
||
|
|
res['dd'] == ['1', '2'],
|
||
|
|
res['extra'] == ('test', 'e1', 'e2', 'e3')
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def retTrue():
|
||
|
|
afo = newAFO(['script.py', 'main', '-b', 'newflag'])
|
||
|
|
afo.run()
|
||
|
|
assert afo.result['bb'] == 'newflag', afo.result
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def onFail():
|
||
|
|
afo = newAFO(['script.py', 'main', 'extra', 'extra'])
|
||
|
|
try:
|
||
|
|
res = afo.run()
|
||
|
|
assert false, f'should not be reached: {res}'
|
||
|
|
except CLIError as e:
|
||
|
|
assert re.search(r'Subparser .* does not accept extra arguments', str(e))
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def onFail():
|
||
|
|
afo = newAFO(['script.py', 'main', 'extra', 'extra'])
|
||
|
|
try:
|
||
|
|
res = afo.run()
|
||
|
|
assert false, f'should not be reached: {res}'
|
||
|
|
except CLIError as e:
|
||
|
|
assert re.search(r'does not accept extra arguments', str(e)), str(e)
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def extraArgs():
|
||
|
|
afo = newAFO(['script.py', 'cli1', '1', '2'])
|
||
|
|
res = afo.run()
|
||
|
|
assert res['extra'] == ('1', '2')
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def listArg():
|
||
|
|
afo = newAFO([
|
||
|
|
'script.py', 'listInput', '-a', 'a1', 'e1', '-b'
|
||
|
|
'b1', '-a', 'a2', 'e2', '-c', 'c1', '-a', 'a3', '-c', 'c2', 'e3'
|
||
|
|
])
|
||
|
|
res = afo.run()
|
||
|
|
del res['_']
|
||
|
|
Assert(res).eq({'aa': ['a1', 'a2', 'a3'], 'bb': 'b1', 'cc': 'c2', 'extra': ('e1', 'e2', 'e3')})
|
||
|
|
|
||
|
|
@dataclass(slots=True, kw_only=True, frozen=False)
|
||
|
|
class CLI2:
|
||
|
|
def f1(_, test: bool = False):
|
||
|
|
assert defl.amCliEntryPoint() is test
|
||
|
|
_.f2()
|
||
|
|
_.f3()
|
||
|
|
|
||
|
|
def f2(_, test: bool = False):
|
||
|
|
assert defl.amCliEntryPoint() is test
|
||
|
|
_.f3()
|
||
|
|
|
||
|
|
def f3(_, test: bool = False):
|
||
|
|
assert defl.amCliEntryPoint() is test
|
||
|
|
|
||
|
|
@dataclass(slots=True, kw_only=True, frozen=False)
|
||
|
|
class CLI3:
|
||
|
|
def f1(_, ):
|
||
|
|
assert defl.amCliEntryPoint()
|
||
|
|
CLI_STEM = True
|
||
|
|
_.f2()
|
||
|
|
_.f3()
|
||
|
|
|
||
|
|
def f2(_, ):
|
||
|
|
assert defl.amCliEntryPoint()
|
||
|
|
CLI_STEM = True
|
||
|
|
_.f3()
|
||
|
|
|
||
|
|
def f3(_, ):
|
||
|
|
assert defl.amCliEntryPoint()
|
||
|
|
_.f4()
|
||
|
|
|
||
|
|
def f4(_, test: bool = False):
|
||
|
|
assert defl.amCliEntryPoint() is test
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def testEntryPoint():
|
||
|
|
part = partial(defl.cliAutoParse, CLI2, runNow=True, autoExit=False)
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f1', '-t'])
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f2', '-t'])
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f3', '-t'])
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def testEntryPoint():
|
||
|
|
part = partial(defl.cliAutoParse, CLI3, runNow=True, autoExit=False)
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f1'])
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f2'])
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f3'])
|
||
|
|
res = part(exclude=['excludeMe'], parseIn=['script.py', 'f4', '-t'])
|
||
|
|
|
||
|
|
@tester.add()
|
||
|
|
def argTypeHint():
|
||
|
|
|
||
|
|
C = NewType('test', list[int])
|
||
|
|
D = GenericAlias(int, list[int])
|
||
|
|
|
||
|
|
class Color(enum.StrEnum):
|
||
|
|
Red = enum.auto()
|
||
|
|
Green = enum.auto()
|
||
|
|
Blue = enum.auto()
|
||
|
|
|
||
|
|
@dataclass(slots=True, kw_only=True, frozen=False)
|
||
|
|
class CLI:
|
||
|
|
def a(a: int):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def b(a: Color):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def c(a: C):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def d(a: D):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def e(a: bool):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def f(a: str):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def g(a: pathlib.Path):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def h(a: ArgTypeHint(typ=int)):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def i(a: ArgTypeHint(typ=int, choices=[1, 2, 3])):
|
||
|
|
return a
|
||
|
|
|
||
|
|
def j(a: ArgTypeHint(typ=int, choices=[1, 2, 3], default=1)):
|
||
|
|
return a
|
||
|
|
|
||
|
|
res = partial(defl.cliAutoParse, CLI, runNow=True, autoExit=False)
|
||
|
|
|
||
|
|
log.info(tester.run())
|
||
|
|
tester.exitWithStatus()
|