# -*- coding: utf-8 -*-
from setuptools import setup

package_dir = \
{'': 'src'}

packages = \
['_garnet',
 '_garnet.events',
 '_garnet.filters',
 '_garnet.helpers',
 '_garnet.storages',
 '_garnet.vars',
 'garnet',
 'garnet.filters']

package_data = \
{'': ['*']}

install_requires = \
['Telethon>=1.10.8,<2.0.0']

setup_kwargs = {
    'name': 'telegram-garnet',
    'version': '0.5.0',
    'description': 'garnet - cool add-on for Telethon',
    'long_description': '\ngarnet\n######\n\nAbout\n*****\n\ngarnet is a ridiculously simple library created mainly for managing your stateful telegram bots written with Telethon.\n\n.. invisible-content\n.. _aiogram: https://github.com/aiogram/aiogram\n\n\n\n***************\nHow to install?\n***************\n\nAlthough, garnet is ``garnet``, it is named ``telegram-garnet`` on the PyPI, you\'ll have to tell that to pip.\n\n``pip install telegram-garnet``\n\n\n*************\nLet\'s dive in\n*************\n\n.. code:: python\n\n    # export BOT_TOKEN, APP_ID, APP_HASH, SESSION_DSN env vars.\n    from garnet import ctx\n    from garnet.events import Router\n    from garnet.filters import State, text, group\n    from garnet.storages import DictStorage\n\n    router = Router()\n    UserStates = group.Group.from_iter(["echo"])  # declare users states\n\n    # register handler for "/start" commands for users with none yet set state\n    @router.message(text.commands("start"), State.entry)\n    async def entrypoint(event):\n        await event.reply("You entered echo zone!\\n/cancel to exit")\n        fsm = ctx.CageCtx.get()\n        await fsm.set_state(UserStates.echo)\n\n    # register handler for "/cancel" commands for users that have entered any state\n    @router.message(text.commands("cancel"), State.any)\n    async def cancel(event):\n        await event.reply("Cancelled :)\\n/start to restart")\n        await ctx.CageCtx.get().set_state(None)\n\n    # handle any message from users with state=UserState.echo\n    @router.message(State.exact(UserStates.echo))\n    async def echo(event):\n        await event.reply(event.text)\n\n    if __name__ == "__main__":\n        from garnet.runner import run, launch\n        launch("my-first-garnet-app", run(router, DictStorage()))\n\n************\nKey features\n************\n\nFilters\n=======\n\nBasically ``Filter`` is a "lazy" callable which holds an optional single-parameter function.\nFilters are event naive and event aware. Filters are mutable, they can migrate from event-naive to event-aware in garnet.\n\nPublic methods\n--------------\n\n- ``.is_event_naive -> bool``\n- ``.call(e: T, /) -> Awaitable[bool]``\n\nInitializer\n^^^^^^^^^^^\n\n``Filter(function[, event_builder])``\n\nValue of the parameter ``function`` must be function that takes exactly one argument with type `Optional[Some]` and\nreturns ``bool`` either True or False.\n\nPossible operations on Filter instances\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n(those are, primarily logical operators)\n\nBinary\n""""""\n\n- ``&`` is a logical AND for two filters\n- ``|`` is a logical OR for two filters\n- ``^`` is a logical XOR for two filters\n\nUnary\n"""""\n\n- ``~`` is a logical NOT for a filter\n\nExamples\n---------\n\n.. code:: python3\n\n    from garnet import Filter, events\n\n    async def fun(_): ...\n\n    # example of event aware filter\n    Filter(fun, events.NewMessage)\n\n    # example of event-naive\n    Filter(fun)\n\nBy default ``Filter`` is event-naive, however when using with ``garnet::Router`` for handlers it may be changed.\n\nFilters "from the box"\n----------------------\n\nText filters\n^^^^^^^^^^^^\n\nOperations on ``Filter((e: Some) -> bool); Some.raw_text or Some.text``\n\nImport\n""""""\n\n``from garnet.filters import text``\n\nLittle journey\n""""""""""""""\n\n- ``text.Len`` is a special class for ``len(Some.raw_text ... "")`` operations. Supports logical comparison operations, such are ``==``, ``>``, ``>=``, ``<``, ``<=``\n\n- ``text.startswith(prefix: str, /)`` will evaluates to ``Some.raw_text.startswith(prefix)``\n\n- ``text.commands(*cmds: str, prefixes="/", to_set=True)`` will evaluate to check if command is within ``cmd`` (ignores mentions, and works on `Some.text`)\n\n- ``text.match(rexpr: str, flags=0, /)`` will evaluate to ``re.compile(rexpr, flags).match(Some.raw_text)``\n\n- ``text.between(*texts: str, to_set=True)`` will evaluate to ``Some.raw_text in texts``\n\n- ``text.can_be_int(base=10)`` will evaluate to ``try{int(Some.raw_text);return True;}except(ValueError){return False;}``\n\n- ``text.can_be_float()`` similarly to ``text.can_be_int`` but for floats.\n\n\nState filters\n^^^^^^^^^^^^^\n\nOperations on users\' states.\n\nImport\n""""""\n\n``from garnet.filters import State``\n\nLittle journey\n""""""""""""""\n\n- ``State.any`` will evaluate to match any state but not ``None``\n- ``State.entry`` will evaluate to ``True`` if only current state is ``None``\n- ``State.exact(state: GroupT | M | "*")`` when "*" is passed will use ``State.any``, when states group is passed will check if current state is any states from the group, when state group member (``M``) passed will check if current state is exactly this state\n- ``State == {some}`` will call ``State.exact(state=some)``\n\nNote\n""""\n\nState filter has effect on ``garnet.ctx.MCtx``.\nAnd if you\'re not sure what are you doing try not to apply logical operators on ``State`` filters.\nSimply, don\'t do ``~State.any`` or ``~State.exact(...some...)``\n\n\nStates declaration\n^^^^^^^^^^^^^^^^^^\n\nImport\n""""""\n\n``from garnet.filters import group``\n\ngroup.M (state group Member)\n""""""""""""""""""""""""""""\n\n*yes, "M" stands for member.*\n\n- ``.next`` return the next ``M`` in the group or raise ``group.NoNext`` exception\n- ``.prev`` return the previous ``M`` in the group or raise ``group.NoPrev`` exception\n- ``.top`` return the top (head) ``M`` in the group or raise ``group.NoTop`` exception\n\ngroup.Group\n"""""""""""\n\nGroup of state members declared as a class (can be nested)\n\n- ``.first`` returns (``M``) the first declared member\n- ``.last`` returns (``M``) the last declared member\n\n**Note**\n``.first`` and ``.last`` are reserved "keywords" for state\n\nUsage\n"""""\n\n.. code:: python\n\n    from garnet.filters import group, State\n\n    class Users(group.Group):\n        ask_name = group.M()\n        ask_age = group.M()\n\n        class Pet(group.Group):\n            ask_name = group.M()\n            ask_age = group.M()\n\n        class Hobby(group.Group):\n            frequency = group.M()\n            ask_if_popular = group.M()\n\n    # 💫 just imagine we already have router 💫\n\n    @router.default(State.exact(Users))  # will handle all states in "Users"\n    # --- some code ---\n    @router.default(State.exact(Users.Pet.ask_age))  # will handle only if current state is equal to "Users.Pet.ask_age"\n    # --- some code ---\n\n\nNote\n""""\n\nThink of groups as an immutable(not really...) linked list of connected group members\nAs you can see in the example above we use nested states groups.\nOne thing about about ``M.[next/prev/top]``.\nWe can go to ``Users.Pet.ask_name`` from ``Users.ask_age`` using ``Users.ask_age.next``,\nbut not backwards as someone could expect with ``Users.Pet.ask_name.prev`` (will actually raise ``NoPrev``)\nNested group members do not know anything about upper members, but they have "owners" which have access to their parent groups and\nin order to access parent of owner of ``x = Users.Pet.ask_name``, we would use ``x.owner``\n\nCallback query (QueryBaker)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOperations on callback queries. Baker is a `callback_data` string generator/parser/validator. ``garnet.ctx::Query`` has\ncontext value which is set after every successful validation.\n\nImport\n""""""\n\n``from garnet.filters import QueryBaker``\n\nLittle journey\n""""""""""""""\n\n- ``(prefix:str, /, *args:str, [ignore:Iterable[QItem]=(),][sep:str="\\n",][maxlen:int=64])`` initializer function, if you want to have custom types in QueryDict\n- ``.filter(extend_ignore:Iterable[str]=(), /, **config)`` will make sure user given callback data is valid by given config.\n- ``.get_checked(**non_ignored:Any)`` will return a string based on passed passed args\n\nUsage\n"""""\n\n.. code:: python\n\n    from garnet.filters import QueryBaker\n\n    qb = QueryBaker(\n        "v",  # set v string as identity(prefix) for our baker\n        ("id", uuid.UUID),  # make uuid.UUID a factory for id arg\n        "act",\n        ignore=("id",),  # mark id arg as `optional`\n        sep=":",  # set a separator for arg values, better not change\n        maxlen=64,  # get_checked will check the length of generated callback and tell you if it\'s more than maxlen\n    )\n    # create v:{id}:{act} pattern\n\n    qb.filter(act="apply")\n    # will be a filter to match queries like "v:(.*):apply"\n\n    qb.get_checked(id="51b3f442-a9f6-4dcc-918e-1f08b1189386", act="clear")\n    # will produce a "safe" string pattern v:51b3f442-a9f6-4dcc-918e-1f08b1189386:clear\n\n    # You\'ll use\n    # .get_checked\n    Button.inline()\n\nNote\n""""\n\nDon\'t use separator string inside your arg values.\n\nTo reuse validated data from filter, use `Query (validated dict)`_\n\nRouters\n=======\n\nRouter (routing table) is a collection of handlers.\n\nPublic methods\n--------------\n\nThose consist mainly from decorators.\n\nInitializer\n^^^^^^^^^^^\n\n``Router(default_event=None, *filters)``\n\n- ``default_event`` default event builder for router\n- ``*filters`` router filters, in order to get into handlers, event should pass these filters.\n\nDecorators\n^^^^^^^^^^\n\nDepending on ``event_builder`` of a decorator, filters inherit that event builder mutating themselves.\n\n- ``.default(*filters)`` event builder is default Router(**this**, ...), should not be None, must implement ``telethon.common::EventBuilder``\n\n- ``.message(*filters)`` shortcut decorator for event builder ``garnet.events::NewMessage``\n\n- ``.callback_query(*filters)`` shortcut decorator for event builder ``garnet.events::CallbackQuery``\n\n- ``.chat_action(*filters)`` shortcut decorator for event builder ``garnet.events::ChatAction``\n\n- ``.message_edited(*filters)`` shortcut decorator for event builder ``garnet.events::MessageEdited``\n\n- ``.on(event_builder, /, *filters)`` pass any event builder (preferably from ``garnet.events::*``)\n\n- ``.use()`` use this decorator for intermediates that are called after filters\n\netc.\n^^^^\n\n- ``.add_use(intermediate, /)`` register an intermediate which will be called after filters for handlers\n- ``.register(handler, filters, event_builder)`` register handler with binding filters and event_builder to it.\n- ``.include(router, /)`` "include" passed router in the callee as its child router\n\n\nExamples\n--------\n\nSimple cases\n^^^^^^^^^^^^\n\n.. code:: python\n\n    from garnet import Router, events, Filter\n\n    router = Router(events.NewMessage, Filter(lambda _: True), Filter(lambda _: True))\n\n    @router.default(Filter(lambda _: True))\n    async def handler(_): pass\n\nNested routers and a little intermediate\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n    from my_project.routers import public_router, admin_router\n    from my_project.logging import put_event\n\n    from garnet import Router, events\n\n    common_router = Router().include(public_router).include(admin_router)\n\n    @common_router.use()\n    async def intermediate(handler, event):\n        await put_event(event, nowait=True)\n        await handler(event)\n\n\nContext variables\n=================\n\nUsers states\n------------\n\n``from garnet.ctx import StateCtx, MCtx``\n\n``MCtx`` is context variable that points to the current states group member (use it carefully)\nit\'s set in ``State`` filters\n\n\n``StateCtx`` points to ``garnet.event::UserCage``\n\n\nUser and chat IDs\n-----------------\n\n``from garnet.ctx import UserIDCtx, ChatIDCtx``\n\nThose will be set after router filters and before handler filters and handlers calls.\n\nHandler\n-------\n\n``from garnet.ctx import HandlerCtx``\n\n``HandlerCtx`` points to currently executing handler.\n\nQuery (validated dict)\n----------------------\n\nData that is stored in Dict[str(arg name), T(arg type from arg-factory(arg-str)->T)]\n\n``from garnet.ctx import Query``\n\n\nNote\n----\n\nUsual contextual variables, with ``.get()``, ``.set()``, ``.reset()`` methods. You\'ll always end up using ``.get()``.\nWork with those only in handlers or handler filters.\n\nAlso every event builder in ``garnet.events`` is "contextfull", but for ``get``, ``set``, ``reset`` you shall add ``_current``\npostfix.\n\nTry to use context variables everywhere not depending on other mechanisms, because they work as you want.\n\n******************\n🦾 Hacking garnet\n******************\n\nGarnet consists of two interfaces `_garnet` and `garnet`, `garnet` is a "public" interface that should have somewhat stable interfaces\nand `_garnet` which is `internal` and considered as `non-public`\n\nInstall and get started\n=======================\n\n::\n\n    git clone git@github.com:ukinti/garnet.git garnet\n    poetry install --dev\n    poetry shell\n\n\nApplying code-style\n===================\n\n::\n\n    # simply\n    make lint\n\n\n*********************\n💬 Contacts/Community\n*********************\n\nJoin our small `group <https://t.me/tg_garnet>`_\n',
    'author': 'Martin Winks',
    'author_email': 'cat@snejugal.ru',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/uwinx/garnet',
    'package_dir': package_dir,
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
