#!/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): print('CLI2.f1') assert defl.amCliEntryPoint() is test, (defl.amCliEntryPoint(), test) _.f2() _.f3() def f2(_, test: bool = False): print('CLI2.f2') assert defl.amCliEntryPoint() is test, (defl.amCliEntryPoint(), test) _.f3() def f3(_, test: bool = False): print('CLI2.f3') assert defl.amCliEntryPoint() is test, (defl.amCliEntryPoint(), test) @dataclass(slots=True, kw_only=True, frozen=False) class CLI3: def f1(_): print('CLI3.f1') assert defl.amCliEntryPoint() CLI_STEM = True _.f2() _.f3() def f2(_): print('CLI3.f2') assert defl.amCliEntryPoint() CLI_STEM = True _.f3() def f3(_): print('CLI3.f3') assert defl.amCliEntryPoint() _.f4() def f4(_, test: bool = False): print('CLI3.f4') assert defl.amCliEntryPoint() is test, (defl.amCliEntryPoint(), 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()