"""
Annotation-based argparse
=========================

:author:
    Ta-Shun Su

This module provide a way to integrate python ``argparse`` module into class attribute
and annotation type, that allow options have type information, and allow parser combination
easily.

class Argument and function argument
------------------------------------

A simple option class that contains several options carried by their attributes.

>>> class ExampleOptions:
...     ANIMAL: str = argument('--ANIMAL')
...     EXP_DATE: str = argument('--EXP_DATE')
...     OUTPUT_DIR: str = argument()

In ``ExampleOptions``, ``ANIMAL`` is an attribute with type annotation ``str``. it has
a class variable :class:`~neuralib.argp.core.Argument` (:func:`~neuralib.argp.core.argument()` return) which contains the
arguments of ``ArgumentParser.add_argument``. For now, we have an optional
argument ``--ANIMAL`` which accept one argument. ``EXP_DATE`` as is another optional argument.
And ``OUTPUT_DIR`` are a positonal argument because it doesn't have dashed options.

>>> opt = parse_args(ExampleOptions())
... print(opt.ANIMAL)

After class declared, you can use :meth:`~neuralib.argp.core.parse_args()` to parse cli arguments. This
function will create an ``ArgumentParser`` and find out all argument attributes.
Then set the attributes from parsed result.

Commandline usage
~~~~~~~~~~~~~~~~~

In bash, you can call this option class ::

    python -m module.path --ANIMAL name --EXP_DATE date output

:func:`~neuralib.argp.core.print_help()` does the similar things but print the help document to the stdout.

>>> print_help(opt)

Or use ``-h`` options ::

    python -m module.path -h


Annotation type infering
~~~~~~~~~~~~~~~~~~~~~~~~

In general, you can think :func:`~neuralib.argp.core.argument` just a delegate funcion that passes the arguments
to the ``ArgumentParser.add_argument``. However, this function will try to
infer missing arguments based on the annotation type when creating the ``ArgumentParser``.
For now, this module is not powerful to handle all possible case. there are support type
(Please see :meth:`~neuralib.argp.core.Argument.complete_kwargs()` for detailed):

1. ``bool``: infer parameter ``action`` to ``store_true``, ``default`` to ``False``.
2. ``Literal[...]`` : infer parameter ``choices``.
3. ``Optional[T]``: infer parameter ``type`` to ``T`` if it is callable. If it is ``Literal``, apply 2.
4. ``callable(...)`` with signature ``Callable[[str], T]``: infer parameter ``type`` to ``T``
5. parameter ``dest`` always use the attribute name.

Additionally, :class:`~neuralib.argp.core.Argument` also provide a parameter ``group`` to reduce the complexity of
create subgrouping parser.

Option class compose
~~~~~~~~~~~~~~~~~~~~

Option class can be composed by inherition. Child option class can also change the value from parent's
argument. As well as disable it (by replacing a value)

>>> class MoreOptions(ExampleOptions):
...     # additional optional option
...     verbose: bool = argument('-v', '--verbose')
...     # change default value
...     ANIMAL: str = as_argument(ExampleOptions.animal).with_options(default='YW00')
...     # and disable an option
...     OUTPUT_DIR: str = 'output' # just replace with a value

Change options name is more complicate, because you might want to add more name, remove some name,
or rename some name. :meth:`~neuralib.argp.core.Argument.with_options()` allow you to do that:

>>> class ChangeExample(ExampleOptions):
...     # replace option name: --animal
...     ANIMAL: str = as_argument(ExampleOptions.animal).with_options('--animal')
...     # add more option name: -A, --ANIMAL
...     ANIMAL: str = as_argument(ExampleOptions.animal).with_options(..., '-A')
...     # rename option: --animal
...     ANIMAL: str = as_argument(ExampleOptions.animal).with_options({
...         '--ANIMAL': '--animal'
...     })

Utility function for option class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``ExampleOptions`` doesn't declated neither ``__str__`` nor ``__repr__``, so it is not convenient to debug.
funciont :func:`~neuralib.argp.core.as_dict()` provide a way to take argument attribute's value into a dictionary.

>>> as_dict(opt)
{'ANIMAL': ..., 'EXP_DATE': ..., 'OUTPUT_DIR': ...}

Option class is not restricted into only one use case. It works like a normal class.

class AbstractParser
--------------------

This class provide a main like class that has more control on ``argparse.ArgumentParser``
creation.

>>> class ExampleParser(AbstractParser, ExampleOptions):
...     DESCRIPTION = 'Example parser'
...     def run(self):
...         ...
>>> if __name__ == '__main__':
...     ExampleParser().main()

Subcommands
-----------

This module isn't fully support sub-command feature, but only provide a simple way for specific case:

>>> parse_command_args(
...     description='top level parser',
...     parsers=dict(ep=ExampleParser)
... )

In bash::

    python -m module.path ep --ANIMAL name --EXP_DATE date output


Validator usage
-----------------------
The validator system provides a fluent, chainable API for building type-specific validation rules.
It works by defining specialized “builder” classes (e.g., for strings, integers, floats, lists, and tuples)
that let you specify constraints like numeric ranges, string length ranges, regex checks, or container length/item rules

See detailed in :attr:`~neuralib.argp.validator`


"""
from ._type import *
from ._validator import *
from .core import *
