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

modules = \
['lazex']
install_requires = \
['astunparse>=1.6.3,<2.0.0']

setup_kwargs = {
    'name': 'lazex',
    'version': '0.4.1',
    'description': 'Allow functions to examine and modify the AST of their arguments',
    'long_description': '![lazex logo](https://raw.githubusercontent.com/L3viathan/lazex/master/logo.jpg)\n\n`lazex` (short for "lazy expressions") is a small library that enables your\nfunctions to customize how arguments are given to it. For example, such a\nfunction `foo` that you call with an expression, say `foo(2 + 3)` does not just\nget the value `5` as a parameter, but knows the 5 came from adding `2` and `3`.\nIn fact, it can even prevent evaluation of this expression and do whatever it\nwants with it.\n\nIn order to use this, you need to import the library:\n\n    import lazex\n\nNext, decorate your functions with the decorator `lazex.me`. This is an\nidentity function (the equivalent of `lambda x: x`):\n\n```python\n@lazex.me\ndef foo(something):\n    return something.evaluate()\n```\n\nA more realistic function might inspect the arguments and tell you how the\nresult was obtained:\n\n```python\n@latex.me\ndef explain(expr):\n    return f"The answer to {expr.escaped} is [expr.evaluate()}"\n```\n\n\nIf you now call `explain(2+3)`, it will return you the string "The answer to (2\n\\+ 3) is 5".\n\nHere\'s the catch: In order for this to work, the calling function must _also_\nhave been decorated with `latex.me`. This is sadly a consequence of the fact\nthat what we\'re doing here is not usually possible in Python.\n\n----\n\nEven without this decorator, you can still use `lazex` to define expressions in\na given context that you may or may not want to evaluate later:\n\n```python\ndef outer():\n    x = 7\n    y = lazex.Expression(\'x * 14\')\n    inner(y)\n\ndef inner(expr):\n    import random\n    if random.random() > 0.5:\n        print(expr.evaluate())\n```\n\nWhile this toy example may seem silly, you can wrap arbitrarily complex [expressions](https://docs.python.org/3/reference/expressions.html). For example, you could delay a complex function call that might need to occur:\n\n```python\nid_ = \'1274897d129d24\'\ndoc = complex.framework.get_document_by_id(id_, reticulate_splines=\'force\')\ndelete = lazex.Expression(\n    \'complex.framework.delete_from_db(\'\n    \'doc, id_, safe_delete=True, cleanup=False,\'\n    \'restore_function=complex.framework.restore_with_trick).confirmation_id\'\n)\nprocess_document(doc, deleteexpr=delete)\n```\n\nHere, the `process_document` function may at some point decide that it needs to\ndelete the document, and can just call `delete.evaluate()`. Sure, this is also\nsolvable with a simple lambda, but if you were looking for straightforward,\nidiomatic solutions you\'ve come to the wrong place.\n\n## Technical details\n\nIf you want to know how this works, just look at `lazex.py`, it\'s barely more\nthan a hundred lines. Here\'s the gist:\n\n- Register all functions decorated by `lazex.me` as magical lazex functions,\n  and replace them with a wrapper.\n- When the wrapper is run, we check if we have already patched the function. If\n  so, we just call the original function with the given arguments.\n- Otherwise, we patch it:\n    - First, we use `inspect.getsource()` to get the original source code of\n      the function.\n    - Next, we parse the source code with `ast.parse()`. We then walk over all\n      nodes of the AST, until we find an `ast.Call` (a function call):\n    - We use the `astunparse` library to get a string representation\n      of all expressions that are given as positional and keyword arguments,\n      and replace the corresponding nodes with yet another `ast.Call` node that\n      will call `lazex.build_expression`.\n    - Effectively, this will replace a call like `foo(1 + 2, x, bar=bat)` with\n      `foo(build_expression("foo", "1 + 2"), build_expression("foo", "x"), bar=build_expression("foo", "bat"))`.\n    - The modified AST is then fed to `astunparse` to generate source code,\n      that source code is executed using `compile()` and the resulting code\n      object is set as the new `.__code__` attribute of the original function.\n- We then execute the patched function with the original arguments. When we\n  reach a call to `build_expression`, we use `inspect.currentframe().f_back` to\n  get a reference to the _previous_ execution frame, and use it to construct an\n  `Expression`. The frame helps us retrieve its local and global namespace (the\n  `.f_locals` and `.f_globals` attributes on the frame object).\n- After saving these namespaces on our `Expression` instance, we can now use it\n  to access the original source code, as well as the AST for this specific\n  expression. When we want to evaluate the expression, we can use the\n  namespaces with `eval` in order to produce the correct values.\n- After having created the `Expression` objects, we then evaluate the name of\n  the function that is supposed to be called with (possibly) escaped arguments.\n  If it is _not_ a registered lazex function, we evaluate the arguments\n  immediately.\n- Also, we search the `Expression`\'s AST for our `build_expression` hooks and\n  remove them: They are unnecessary here and just leak implementation details.\n',
    'author': 'L3viathan',
    'author_email': 'git@l3vi.de',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/L3viathan/lazex',
    'py_modules': modules,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
