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

packages = \
['gardener']

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

setup_kwargs = {
    'name': 'gardener',
    'version': '2.0.1',
    'description': 'Hook-based tree manipulation library',
    'long_description': '[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/courage-tci/gardener/build.yml?branch=pub)](https://github.com/courage-tci/gardener/actions/workflows/build.yml)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/courage-tci/gardener/test.yml?branch=pub&label=tests)](https://github.com/courage-tci/gardener/actions/workflows/test.yml)\n[![PyPI](https://img.shields.io/pypi/v/gardener)](https://pypi.org/project/gardener/)\n![PyPI - Downloads](https://pepy.tech/badge/gardener)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/gardener)\n[![Coveralls](https://img.shields.io/coverallsCoverage/github/courage-tci/gardener?label=test%20coverage)](https://coveralls.io/github/courage-tci/gardener?branch=pub)\n![License](https://img.shields.io/github/license/courage-tci/gardener)\n![Badge Count](https://img.shields.io/badge/badges-8-important)\n\n**gardener** is a simple tree manipulation module. It provides a hook-based tree transformation.\n\n## Installation\n\n`gardener` is available on PyPi:    \n\n`python -m pip install gardener` (or any other package manager)\n\n## Basic example\n\nThis is a simple example usage, a hook that transforms every `tag` node into a string:\n\n\n```python\nfrom gardener import make_node, register_hook, Node\n\n\n@register_hook("tag")\ndef tag_hook(node: Node) -> str:\n    return f"tag:{node[\'i\']}{node[\'children\', []]}"\n\n\nx: str = make_node(\n    "tag",\n    children=[\n        make_node("tag", i=i) \n        for i in range(10)\n    ],\n    i=99\n)\n\nprint(x) # tag:99[\'tag:0[]\', \'tag:1[]\', \'tag:2[]\', \'tag:3[]\', \'tag:4[]\', \'tag:5[]\', \'tag:6[]\', \'tag:7[]\', \'tag:8[]\', \'tag:9[]\']\n```\n\n## Combining hooks and transforming nodes\n\n```python\nfrom gardener import make_node, register_hook, Node\nfrom operator import add, sub, mul, truediv\n\n\noperators = {\n    "+": add,\n    "-": sub,\n    "*": mul,\n    "/": truediv\n}\n\n@register_hook("+", "-", "*", "/")\ndef binary_expr(node: Node) -> float:\n    op = node.key[0] # node.key is a 1-element tuple, e.g. (\'+\', )\n    op_func = operators[op]\n    \n    parts = node["parts", []]\n    \n    if not parts:\n        if op in "+-":\n            return 0 # empty sum\n        return 1 # empty product\n    \n    result = parts[0]\n    \n    for i in range(1, len(parts)):\n        result = op_func(result, parts[i])\n    \n    return result\n\n\nx: float = make_node(\n    "+",\n    parts=[\n        3,\n        make_node("*", parts=[5, 6]) # 30\n    ]\n)\n\nprint(x) # 33\n```\n\nLet\'s add exponentiation to this calculator. The trick is that power is right-associative (so that `2 ** 2 ** 3` equals `2 ** (2 ** 3)`, not `(2 ** 2) ** 3`).    \nYou can obviously write a separate hook for that, but we can just combine hooks:\n\n```python\nfrom gardener import make_node, register_hook, Node\nfrom operator import add, sub, mul, truediv\n\n\noperators = {\n    "+": add,\n    "-": sub,\n    "*": mul,\n    "/": truediv,\n    "**": lambda x, y: pow(y, x) # reversing order there, so that we can reverse the order of all elements\n}\n\n"""\n\nThis hook, instead of producing a new non-node value, just edits the node contents.\nThis allows the hook chain to continue to `binary_expr` hook\n\nBe aware that the order of hook apply is the same as their registration.\n\n"""\n@register_hook("**")\ndef power_reverse(node: Node) -> Node:\n    node["parts"] = node["parts", []][::-1]\n    return node\n\n\n@register_hook("+", "-", "*", "/", "**")\ndef binary_expr(node: Node) -> float:\n    op = node.key[0] # node.key is a 1-element tuple, e.g. (\'+\', )\n    op_func = operators[op]\n    \n    parts = node["parts", []]\n    \n    if not parts:\n        if op in "+-":\n            return 0 # empty sum\n        return 1 # empty product\n    \n    result = parts[0]\n    \n    for i in range(1, len(parts)):\n        result = op_func(result, parts[i])\n    \n    return result\n\n\nx: float = make_node(\n    "+",\n    parts=[\n        3,\n        make_node("*", parts=[5, 6]), # 30\n        make_node("**", parts=[2, 2, 3]) # 256\n    ]\n)\n\nprint(x) # 289\n```\n\nThis, of course, may not be the most efficient or obvious way, but `gardener` doesn\'t impose any restrictions on how you might approach a problem\n\n## Node props\n\nExamples above have shown how to set initial props of a `Node`. To get and edit those props, use bracket notation:\n\n\n```python\nfrom gardener import make_node\n\nnode = make_node("test")\n\nnode["a"] = 10 # accepts any type of value, but key must be a string\nprint(node["a"]) # prints 10\nprint(node["b", 0]) # prints 0 (default value)\nprint(node["b"]) # raises KeyError\n\n```\n\n## Hook evaluation order\n\nHook ordering is simple:\n\n1. Hooks run at the node creation, there is no way to get a node that wasn\'t processed with relevant hooks (if there were any) \n    *except creating a `Node` object directly, which is discouraged*\n2. Hooks are run in registration order, because when you register a hook, it\'s appended to the end of the list for that key. \n    *you can change the order by editing `scope.hooks[key]` directly (check Scopes below)*\n\n## Scoping\n\nOften it is convenient to have different trees in one project, using different hooks.    \nWhile this can be done through namespacing (`make_node` actually also accepts node key as a `str | tuple[str, ...]`), that approach would force you to write long names in node creating and hook registration.    \n\n`gardener` provides you with a more convenient approach: `Scope` objects. A scope is an isolated store with hooks:\n\n```python\nfrom gardener import Scope, Node\n\n\nscope1 = Scope("scope1") # key is optional and it doesn\'t affect scope behaviour\nscope2 = Scope("scope2")\n\n\n@scope1.register_hook("i")\ndef print_stuff_1(node: Node) -> Node:\n    print("this is the first scope")\n    return node\n\n@scope2.register_hook("i")\ndef print_stuff_2(node: Node) -> Node:\n    print("this is the second scope")\n    return node\n\n@scope1.register_hook("i")\n@scope2.register_hook("i")\ndef print_stuff_both(node: Node) -> Node:\n    print("this is both scopes")\n    return node\n\n\n# prints "this is the first scope"\n# prints "this is both scopes"\nscope1.make_node("i")\n\n\n# prints "this is the second scope"\n# prints "this is both scopes"\nscope2.make_node("i")\n```\n\nYou can get all of the scope hooks with `scope.hooks`. It has type `dict[tuple[str, ...], list[HookType]]`.    \nTo get the scope of the current node (e.g. in a hook, use `node.scope`)    \n\nGlobal `make_node` and `register_hook` are, in fact, methods of `gardener.core.default_scope`\n\n\n## Applying hooks multiple times\n\nTo apply a hook to a node multiple times, call `node.transform()` — it would return the result of another chain of transformations.    \n**Be careful about using it in hooks, as this could easily lead to infinite recursion if not handled properly.**    \n\n## Node printing\n\nIf your node props are JSON-serializable, you can run `node.pretty(**dumps_kwargs)` to get a pretty indented JSON representation of the node.    \nNode class itself is JSON-serializable (only with `NodeJSON` as an encoder).\n\nTo represent non-JSON-serializable data, you will need to provide an encoder class:\n\n```python\nfrom gardener import make_node\nfrom gardener.core import NodeJSON\nfrom typing import Any\n\n\nclass SomeCoolDataClass: # your custom class\n    def __init__(self, x: int):\n        self.x = x\n\n\nclass MyNodeJSON(NodeJSON):\n    def default(self, obj: Any):\n        if isinstance(obj, SomeCoolDataClass):\n            return f"SomeCoolDataClass<{obj.x}>" # return any serializable data here (can contain nodes or, e.g. SomeCoolDataClass inside)\n        return super().default(obj)\n\n\nnode = make_node(\n    "cool_data_node",\n    cool_data=SomeCoolDataClass(6)\n)\n\nprint(\n    node.pretty(cls=MyNodeJSON) # accepts same arguments (keyword-only) as json.dumps\n)\n"""\n{\n  "key": "cool_data_node",\n  "props": {\n    "cool_data": "SomeCoolDataClass<6>"\n  }\n}\n"""\n```',
    'author': 'Dmitry Gritsenko',
    'author_email': 'k01419q45@ya.ru',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/courage-tci/gardener',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
