cosmic-ray 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. cosmic_ray-0.0.0/LICENCE.txt +19 -0
  2. cosmic_ray-0.0.0/MANIFEST.in +2 -0
  3. cosmic_ray-0.0.0/PKG-INFO +108 -0
  4. cosmic_ray-0.0.0/README.rst +39 -0
  5. cosmic_ray-0.0.0/pyproject.toml +93 -0
  6. cosmic_ray-0.0.0/setup.cfg +4 -0
  7. cosmic_ray-0.0.0/src/cosmic_ray/__init__.py +1 -0
  8. cosmic_ray-0.0.0/src/cosmic_ray/ast/__init__.py +109 -0
  9. cosmic_ray-0.0.0/src/cosmic_ray/ast/ast_query.py +115 -0
  10. cosmic_ray-0.0.0/src/cosmic_ray/cli.py +307 -0
  11. cosmic_ray-0.0.0/src/cosmic_ray/commands/__init__.py +10 -0
  12. cosmic_ray-0.0.0/src/cosmic_ray/commands/execute.py +53 -0
  13. cosmic_ray-0.0.0/src/cosmic_ray/commands/init.py +90 -0
  14. cosmic_ray-0.0.0/src/cosmic_ray/commands/new_config.py +54 -0
  15. cosmic_ray-0.0.0/src/cosmic_ray/config.py +115 -0
  16. cosmic_ray-0.0.0/src/cosmic_ray/distribution/__init__.py +1 -0
  17. cosmic_ray-0.0.0/src/cosmic_ray/distribution/distributor.py +15 -0
  18. cosmic_ray-0.0.0/src/cosmic_ray/distribution/http.py +175 -0
  19. cosmic_ray-0.0.0/src/cosmic_ray/distribution/local.py +32 -0
  20. cosmic_ray-0.0.0/src/cosmic_ray/exceptions.py +4 -0
  21. cosmic_ray-0.0.0/src/cosmic_ray/modules.py +38 -0
  22. cosmic_ray-0.0.0/src/cosmic_ray/mutating.py +193 -0
  23. cosmic_ray-0.0.0/src/cosmic_ray/operators/__init__.py +0 -0
  24. cosmic_ray-0.0.0/src/cosmic_ray/operators/binary_operator_replacement.py +90 -0
  25. cosmic_ray-0.0.0/src/cosmic_ray/operators/boolean_replacer.py +91 -0
  26. cosmic_ray-0.0.0/src/cosmic_ray/operators/break_continue.py +17 -0
  27. cosmic_ray-0.0.0/src/cosmic_ray/operators/comparison_operator_replacement.py +104 -0
  28. cosmic_ray-0.0.0/src/cosmic_ray/operators/exception_replacer.py +60 -0
  29. cosmic_ray-0.0.0/src/cosmic_ray/operators/keyword_replacer.py +27 -0
  30. cosmic_ray-0.0.0/src/cosmic_ray/operators/no_op.py +26 -0
  31. cosmic_ray-0.0.0/src/cosmic_ray/operators/number_replacer.py +43 -0
  32. cosmic_ray-0.0.0/src/cosmic_ray/operators/operator.py +90 -0
  33. cosmic_ray-0.0.0/src/cosmic_ray/operators/provider.py +61 -0
  34. cosmic_ray-0.0.0/src/cosmic_ray/operators/remove_decorator.py +26 -0
  35. cosmic_ray-0.0.0/src/cosmic_ray/operators/unary_operator_replacement.py +118 -0
  36. cosmic_ray-0.0.0/src/cosmic_ray/operators/util.py +23 -0
  37. cosmic_ray-0.0.0/src/cosmic_ray/operators/variable_inserter.py +80 -0
  38. cosmic_ray-0.0.0/src/cosmic_ray/operators/variable_replacer.py +101 -0
  39. cosmic_ray-0.0.0/src/cosmic_ray/operators/zero_iteration_for_loop.py +28 -0
  40. cosmic_ray-0.0.0/src/cosmic_ray/plugins.py +77 -0
  41. cosmic_ray-0.0.0/src/cosmic_ray/progress.py +129 -0
  42. cosmic_ray-0.0.0/src/cosmic_ray/testing.py +56 -0
  43. cosmic_ray-0.0.0/src/cosmic_ray/timing.py +43 -0
  44. cosmic_ray-0.0.0/src/cosmic_ray/tools/__init__.py +0 -0
  45. cosmic_ray-0.0.0/src/cosmic_ray/tools/badge.py +44 -0
  46. cosmic_ray-0.0.0/src/cosmic_ray/tools/filters/__init__.py +0 -0
  47. cosmic_ray-0.0.0/src/cosmic_ray/tools/filters/filter_app.py +74 -0
  48. cosmic_ray-0.0.0/src/cosmic_ray/tools/filters/git.py +96 -0
  49. cosmic_ray-0.0.0/src/cosmic_ray/tools/filters/operators_filter.py +69 -0
  50. cosmic_ray-0.0.0/src/cosmic_ray/tools/filters/pragma_no_mutate.py +66 -0
  51. cosmic_ray-0.0.0/src/cosmic_ray/tools/html.py +310 -0
  52. cosmic_ray-0.0.0/src/cosmic_ray/tools/http_workers.py +124 -0
  53. cosmic_ray-0.0.0/src/cosmic_ray/tools/report.py +57 -0
  54. cosmic_ray-0.0.0/src/cosmic_ray/tools/survival_rate.py +77 -0
  55. cosmic_ray-0.0.0/src/cosmic_ray/tools/xml.py +87 -0
  56. cosmic_ray-0.0.0/src/cosmic_ray/version.py +4 -0
  57. cosmic_ray-0.0.0/src/cosmic_ray/work_db.py +297 -0
  58. cosmic_ray-0.0.0/src/cosmic_ray/work_item.py +101 -0
  59. cosmic_ray-0.0.0/src/cosmic_ray.egg-info/PKG-INFO +108 -0
  60. cosmic_ray-0.0.0/src/cosmic_ray.egg-info/SOURCES.txt +62 -0
  61. cosmic_ray-0.0.0/src/cosmic_ray.egg-info/dependency_links.txt +1 -0
  62. cosmic_ray-0.0.0/src/cosmic_ray.egg-info/entry_points.txt +18 -0
  63. cosmic_ray-0.0.0/src/cosmic_ray.egg-info/requires.txt +28 -0
  64. cosmic_ray-0.0.0/src/cosmic_ray.egg-info/top_level.txt +1 -0
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015-2017 Sixty North AS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include LICENSE.txt
2
+ include README.md
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.1
2
+ Name: cosmic_ray
3
+ Version: 0.0.0
4
+ Summary: Mutation testing
5
+ Author-email: Sixty North AS <austin@sixty-north.com>
6
+ License: Copyright (c) 2015-2017 Sixty North AS
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ Project-URL: repository, https://github.com/sixty-north/cosmic-ray
27
+ Classifier: Development Status :: 4 - Beta
28
+ Classifier: Environment :: Console
29
+ Classifier: Intended Audience :: Developers
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Classifier: Programming Language :: Python
33
+ Classifier: Programming Language :: Python :: 3.7
34
+ Classifier: Programming Language :: Python :: 3.8
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Classifier: Topic :: Software Development :: Testing
41
+ Requires-Python: >=3.7
42
+ Description-Content-Type: text/x-rst
43
+ License-File: LICENCE.txt
44
+ Requires-Dist: aiohttp
45
+ Requires-Dist: anybadge
46
+ Requires-Dist: click
47
+ Requires-Dist: decorator
48
+ Requires-Dist: exit_codes
49
+ Requires-Dist: gitpython
50
+ Requires-Dist: parso
51
+ Requires-Dist: qprompt
52
+ Requires-Dist: rich
53
+ Requires-Dist: sqlalchemy
54
+ Requires-Dist: stevedore
55
+ Requires-Dist: toml
56
+ Requires-Dist: yattag
57
+ Provides-Extra: test
58
+ Requires-Dist: hypothesis; extra == "test"
59
+ Requires-Dist: pytest; extra == "test"
60
+ Requires-Dist: pytest-mock; extra == "test"
61
+ Provides-Extra: dev
62
+ Requires-Dist: flake8; extra == "dev"
63
+ Requires-Dist: flake8-pyproject; extra == "dev"
64
+ Requires-Dist: ruff; extra == "dev"
65
+ Requires-Dist: bump-my-version; extra == "dev"
66
+ Provides-Extra: docs
67
+ Requires-Dist: sphinx; extra == "docs"
68
+ Requires-Dist: sphinx_rtd_theme; extra == "docs"
69
+
70
+ |Python version| |Python version windows| |Build Status| |Documentation|
71
+
72
+ Cosmic Ray: mutation testing for Python
73
+ =======================================
74
+
75
+
76
+ "Four human beings -- changed by space-born cosmic rays into something more than merely human."
77
+
78
+ -- The Fantastic Four
79
+
80
+ Cosmic Ray is a mutation testing tool for Python 3.
81
+
82
+ It makes small changes to your source code, running your test suite for each
83
+ one. Here's how the mutations look:
84
+
85
+ .. image:: docs/source/cr-in-action.gif
86
+
87
+ |full_documentation|_
88
+
89
+ Contributing
90
+ ------------
91
+
92
+ The easiest way to contribute is to use Cosmic Ray and submit reports for defects or any other issues you come across.
93
+ Please see CONTRIBUTING.rst for more details.
94
+
95
+ .. |Python version| image:: https://img.shields.io/badge/Python_version-3.5+-blue.svg
96
+ :target: https://www.python.org/
97
+ .. |Python version windows| image:: https://img.shields.io/badge/Python_version_(windows)-3.7+-blue.svg
98
+ :target: https://www.python.org/
99
+ .. |Build Status| image:: https://github.com/sixty-north/cosmic-ray/actions/workflows/python-package.yml/badge.svg
100
+ :target: https://github.com/sixty-north/cosmic-ray/actions/workflows/python-package.yml
101
+ .. |Code Health| image:: https://landscape.io/github/sixty-north/cosmic-ray/master/landscape.svg?style=flat
102
+ :target: https://landscape.io/github/sixty-north/cosmic-ray/master
103
+ .. |Code Coverage| image:: https://codecov.io/gh/sixty-north/cosmic-ray/branch/master/graph/badge.svg
104
+ :target: https://codecov.io/gh/Vimjas/covimerage/branch/master
105
+ .. |Documentation| image:: https://readthedocs.org/projects/cosmic-ray/badge/?version=latest
106
+ :target: http://cosmic-ray.readthedocs.org/en/latest/
107
+ .. |full_documentation| replace:: **Read the full documentation at readthedocs.**
108
+ .. _full_documentation: http://cosmic-ray.readthedocs.org/en/latest/
@@ -0,0 +1,39 @@
1
+ |Python version| |Python version windows| |Build Status| |Documentation|
2
+
3
+ Cosmic Ray: mutation testing for Python
4
+ =======================================
5
+
6
+
7
+ "Four human beings -- changed by space-born cosmic rays into something more than merely human."
8
+
9
+ -- The Fantastic Four
10
+
11
+ Cosmic Ray is a mutation testing tool for Python 3.
12
+
13
+ It makes small changes to your source code, running your test suite for each
14
+ one. Here's how the mutations look:
15
+
16
+ .. image:: docs/source/cr-in-action.gif
17
+
18
+ |full_documentation|_
19
+
20
+ Contributing
21
+ ------------
22
+
23
+ The easiest way to contribute is to use Cosmic Ray and submit reports for defects or any other issues you come across.
24
+ Please see CONTRIBUTING.rst for more details.
25
+
26
+ .. |Python version| image:: https://img.shields.io/badge/Python_version-3.5+-blue.svg
27
+ :target: https://www.python.org/
28
+ .. |Python version windows| image:: https://img.shields.io/badge/Python_version_(windows)-3.7+-blue.svg
29
+ :target: https://www.python.org/
30
+ .. |Build Status| image:: https://github.com/sixty-north/cosmic-ray/actions/workflows/python-package.yml/badge.svg
31
+ :target: https://github.com/sixty-north/cosmic-ray/actions/workflows/python-package.yml
32
+ .. |Code Health| image:: https://landscape.io/github/sixty-north/cosmic-ray/master/landscape.svg?style=flat
33
+ :target: https://landscape.io/github/sixty-north/cosmic-ray/master
34
+ .. |Code Coverage| image:: https://codecov.io/gh/sixty-north/cosmic-ray/branch/master/graph/badge.svg
35
+ :target: https://codecov.io/gh/Vimjas/covimerage/branch/master
36
+ .. |Documentation| image:: https://readthedocs.org/projects/cosmic-ray/badge/?version=latest
37
+ :target: http://cosmic-ray.readthedocs.org/en/latest/
38
+ .. |full_documentation| replace:: **Read the full documentation at readthedocs.**
39
+ .. _full_documentation: http://cosmic-ray.readthedocs.org/en/latest/
@@ -0,0 +1,93 @@
1
+ [build-system]
2
+ requires = ['setuptools']
3
+ build-backend = 'setuptools.build_meta'
4
+
5
+ [project]
6
+ name = "cosmic_ray"
7
+ requires-python = ">= 3.7"
8
+ dynamic = ["version"]
9
+ authors = [{ name = "Sixty North AS", email = "austin@sixty-north.com" }]
10
+ description = "Mutation testing"
11
+ readme = { file = "README.rst", content-type = "text/x-rst" }
12
+ license = { file = "LICENCE.txt" }
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3.7",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Software Development :: Testing",
28
+
29
+ ]
30
+ dependencies = [
31
+ "aiohttp",
32
+ "anybadge",
33
+ "click",
34
+ "decorator",
35
+ "exit_codes",
36
+ "gitpython",
37
+ "parso",
38
+ "qprompt",
39
+ "rich",
40
+ "sqlalchemy",
41
+ "stevedore",
42
+ "toml",
43
+ "yattag",
44
+ ]
45
+
46
+ [project.optional-dependencies]
47
+ test = ["hypothesis", "pytest", "pytest-mock"]
48
+ dev = ["flake8", "flake8-pyproject", "ruff", "bump-my-version"]
49
+ docs = ["sphinx", "sphinx_rtd_theme"]
50
+
51
+ [project.scripts]
52
+ cosmic-ray = "cosmic_ray.cli:main"
53
+ cr-html = "cosmic_ray.tools.html:report_html"
54
+ cr-report = "cosmic_ray.tools.report:report"
55
+ cr-badge = "cosmic_ray.tools.badge:generate_badge"
56
+ cr-rate = "cosmic_ray.tools.survival_rate:format_survival_rate"
57
+ cr-xml = "cosmic_ray.tools.xml:report_xml"
58
+ cr-filter-operators = "cosmic_ray.tools.filters.operators_filter:main"
59
+ cr-filter-pragma = "cosmic_ray.tools.filters.pragma_no_mutate:main"
60
+ cr-filter-git = "cosmic_ray.tools.filters.git:main"
61
+ cr-http-workers = "cosmic_ray.tools.http_workers:main"
62
+
63
+ [project.entry-points."cosmic_ray.operator_providers"]
64
+ core = "cosmic_ray.operators.provider:OperatorProvider"
65
+
66
+ [project.entry-points."cosmic_ray.distributors"]
67
+ http = "cosmic_ray.distribution.http:HttpDistributor"
68
+ local = "cosmic_ray.distribution.local:LocalDistributor"
69
+
70
+ [project.urls]
71
+ repository = "https://github.com/sixty-north/cosmic-ray"
72
+
73
+ [tool.setuptools.packages.find]
74
+ where = ["src"]
75
+
76
+ [tool.bumpversion]
77
+ current_version = "8.3.9"
78
+ parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
79
+ serialize = ["{major}.{minor}.{patch}"]
80
+ tag = true
81
+ commit = true
82
+ message = "Bump version: {current_version} → {new_version}"
83
+ tag_name = "release/v{new_version}"
84
+ tag_message = "Bump version: {current_version} → {new_version}"
85
+
86
+ [[tool.bumpversion.files]]
87
+ filename = "src/cosmic_ray/version.py"
88
+
89
+ [tool.flake8]
90
+ max-line-length = 120
91
+
92
+ [tool.ruff]
93
+ line-length = 120
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Cosmic Ray is a mutation testing tool for Python."""
@@ -0,0 +1,109 @@
1
+ "Tools for working with parso ASTs."
2
+
3
+ from abc import ABC, abstractmethod
4
+ import io
5
+
6
+ import parso.python.tree
7
+ import parso.tree
8
+
9
+
10
+ class Visitor(ABC):
11
+ """AST visitor for parso trees.
12
+
13
+ This supports both simple traversal as well as editing of the tree.
14
+ """
15
+
16
+ def walk(self, node):
17
+ "Walk a parse tree, calling visit for each node."
18
+ node = self.visit(node)
19
+
20
+ if node is None:
21
+ return None
22
+
23
+ if isinstance(node, parso.tree.BaseNode):
24
+ walked = map(self.walk, node.children)
25
+ node.children = [child for child in walked if child is not None]
26
+
27
+ return node
28
+
29
+ @abstractmethod
30
+ def visit(self, node):
31
+ """Called for each node in the walk.
32
+
33
+ This should return a node that will replace the node argument in the AST. This can be
34
+ the node argument itself, a new node, or None. If None is returned, then the node is
35
+ removed from the tree.
36
+
37
+ Args:
38
+ node: The node currently being visited.
39
+
40
+ Returns:
41
+ A node or `None`.
42
+ """
43
+
44
+
45
+ def ast_nodes(node):
46
+ """Iterable of all nodes in a tree.
47
+
48
+ Args:
49
+ node: The top node in a parso tree to iterate.
50
+
51
+ Yields:
52
+ All of the nodes in the tree.
53
+ """
54
+ yield node
55
+
56
+ if isinstance(node, parso.tree.BaseNode):
57
+ for child in node.children:
58
+ yield from ast_nodes(child)
59
+
60
+
61
+ def get_ast(module_path):
62
+ """Get the AST for the code in a file.
63
+
64
+ Args:
65
+ module_path: pathlib.Path to the file containing the code.
66
+
67
+ Returns:
68
+ The parso parse tree for the code in `module_path`.
69
+ """
70
+ with module_path.open(mode="rt", encoding="utf-8") as handle:
71
+ source = handle.read()
72
+
73
+ return parso.parse(source)
74
+
75
+
76
+ def is_none(node):
77
+ "Determine if a node is the `None` keyword."
78
+ return isinstance(node, parso.python.tree.Keyword) and node.value == "None"
79
+
80
+
81
+ def is_number(node):
82
+ "Determine if a node is a number."
83
+ return isinstance(node, parso.python.tree.Number)
84
+
85
+
86
+ def dump_node(node):
87
+ "Generate string version of node."
88
+ buffer = io.StringIO()
89
+ write = buffer.write
90
+
91
+ def do_dump(node, indent=""):
92
+ write("{}{}({}".format(indent, type(node).__name__, node.type))
93
+ value = getattr(node, "value", None)
94
+ if value:
95
+ value = value.replace("\n", "\\n")
96
+ write(", '{}'".format(value))
97
+ children = getattr(node, "children", None)
98
+ if children:
99
+ write(", [\n")
100
+ for child in children:
101
+ do_dump(child, indent + " " * 4)
102
+ write(",\n")
103
+ write("{}]".format(indent))
104
+ write(")")
105
+ if not indent:
106
+ write("\n")
107
+
108
+ do_dump(node)
109
+ return buffer.getvalue()
@@ -0,0 +1,115 @@
1
+ "Tools for querying ASTs."
2
+
3
+
4
+ class ASTQuery:
5
+ """
6
+ Allowing to navigate into any object and test attribute of any object:
7
+
8
+ Examples:
9
+ >>> ASTQuery(node).parent.match(Node, type='node').ok
10
+
11
+ Test if node.parent isinstance of Node and node.parent.type == 'node'
12
+ At each step (each '.' (dot)) you receive an ObjTest object, then
13
+
14
+ Navigation:
15
+ You can call any properties or functions of the base object
16
+ >>> ASTQuery(node).parent.children[2].get_next_sibling()
17
+
18
+ Test:
19
+ >>> ASTQuery(node).match(attr='value').match(Class)
20
+ All in once:
21
+ >>> ASTQuery(node).match(Class, attr='value')
22
+
23
+ Conditional navigation:
24
+ >>> ASTQuery(node).IF.match(attr='intermediate').parent.FI
25
+
26
+ Final result:
27
+ >>> ASTQuery(node).ok
28
+ >>> bool(ASTQuery(node))
29
+
30
+ """
31
+
32
+ def __init__(self, obj):
33
+ self.obj = obj
34
+
35
+ def _clone(self, obj) -> "ASTQuery":
36
+ "Clone this query."
37
+ return type(self)(obj)
38
+
39
+ def match(self, cls=None, **kwargs) -> "ASTQuery":
40
+ "Check if node matches a class."
41
+ obj = self.obj
42
+ if obj is None:
43
+ return self
44
+
45
+ if cls is None or isinstance(obj, cls):
46
+ for k, v in kwargs.items():
47
+ op = None
48
+ k__op = k.split("__")
49
+ if len(k__op) == 2:
50
+ k, op = k__op
51
+ node_value = getattr(obj, k)
52
+ if op is None:
53
+ if node_value != v:
54
+ break
55
+ elif op == "in":
56
+ if node_value not in v:
57
+ break
58
+ else:
59
+ raise ValueError("Can't handle operator {}".format(op))
60
+ else:
61
+ # All is true, continue recursion
62
+ return self
63
+
64
+ # A test fails
65
+ return self._clone(None)
66
+
67
+ @property
68
+ def ok(self):
69
+ "Is the query ok."
70
+ return bool(self.obj)
71
+
72
+ def __bool__(self):
73
+ return self.ok
74
+
75
+ def __getattr__(self, item) -> "ASTQuery":
76
+ obj = self.obj
77
+ if obj is None:
78
+ return self
79
+ return self._clone(getattr(obj, item))
80
+
81
+ @property
82
+ def IF(self):
83
+ "Conditional navigation."
84
+ return ASTQueryOptional(self.obj, obj_test=self)
85
+
86
+ def __call__(self, *args, **kwargs) -> "ASTQuery":
87
+ if self.obj is None:
88
+ return self
89
+ return self._clone(self.obj(*args, **kwargs))
90
+
91
+ def __getitem__(self, item) -> "ASTQuery":
92
+ if self.obj is None:
93
+ return self
94
+ return self._clone(self.obj[item])
95
+
96
+
97
+ class ASTQueryOptional(ASTQuery):
98
+ "Manages conditional navigation."
99
+
100
+ def __init__(self, obj, obj_test=None):
101
+ super().__init__(obj)
102
+ self._initial = obj_test
103
+
104
+ def _clone(self, obj):
105
+ o = super()._clone(obj)
106
+ o._initial = self._initial # pylint: disable=protected-access
107
+
108
+ return o
109
+
110
+ @property
111
+ def FI(self):
112
+ "End of conditional navigation."
113
+ if self:
114
+ return self._initial._clone(self.obj) # pylint: disable=protected-access
115
+ return self._initial