Recursively evaluate an AST node to build a Cycler object.
(node)
| 835 | |
| 836 | |
| 837 | def _eval_cycler_expr(node): |
| 838 | """Recursively evaluate an AST node to build a Cycler object.""" |
| 839 | if isinstance(node, ast.BinOp): |
| 840 | left = _eval_cycler_expr(node.left) |
| 841 | right = _eval_cycler_expr(node.right) |
| 842 | if isinstance(node.op, ast.Add): |
| 843 | return left + right |
| 844 | if isinstance(node.op, ast.Mult): |
| 845 | return left * right |
| 846 | raise ValueError(f"Unsupported operator: {type(node.op).__name__}") |
| 847 | if isinstance(node, ast.Call): |
| 848 | if not (isinstance(node.func, ast.Name) |
| 849 | and node.func.id in ('cycler', 'concat')): |
| 850 | raise ValueError( |
| 851 | "only the 'cycler()' and 'concat()' functions are allowed") |
| 852 | func = cycler if node.func.id == 'cycler' else cconcat |
| 853 | args = [_eval_cycler_expr(a) for a in node.args] |
| 854 | kwargs = {kw.arg: _eval_cycler_expr(kw.value) for kw in node.keywords} |
| 855 | return func(*args, **kwargs) |
| 856 | if isinstance(node, ast.Subscript): |
| 857 | sl = node.slice |
| 858 | if not isinstance(sl, ast.Slice): |
| 859 | raise ValueError("only slicing is supported, not indexing") |
| 860 | s = slice( |
| 861 | ast.literal_eval(sl.lower) if sl.lower else None, |
| 862 | ast.literal_eval(sl.upper) if sl.upper else None, |
| 863 | ast.literal_eval(sl.step) if sl.step else None, |
| 864 | ) |
| 865 | value = _eval_cycler_expr(node.value) |
| 866 | return value[s] |
| 867 | # Allow literal values (int, strings, lists, tuples) as arguments |
| 868 | # to cycler() and concat(). |
| 869 | try: |
| 870 | return ast.literal_eval(node) |
| 871 | except (ValueError, TypeError): |
| 872 | raise ValueError( |
| 873 | f"Unsupported expression in cycler string: {ast.dump(node)}") |
| 874 | |
| 875 | |
| 876 | # A validator dedicated to the named legend loc |
no test coverage detected
searching dependent graphs…