From 32e47a232d912e580d63c26f836a62c96e6c69b7 Mon Sep 17 00:00:00 2001 From: bey Date: Thu, 11 Aug 2016 12:08:44 +0300 Subject: [PATCH 1/6] Add rename method with tests. Remove task from TODO list. Update CHANGELOG. --- CHANGELOG | 1 + TODO.md | 1 - redbaron/base_nodes.py | 9 +++++++++ tests/test_initial_parsing.py | 16 ++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7a58b7dc..31d472b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ Changelog - fix some old call to log() weren't lazy, that could cause a crash in some situations by an infinite recursive call and also reduce performances - fix in _iter_in_rendering_order method to avoid bug in edge cases (issue #107) +- add rename method for NameNode and def/class 0.6.1 (2016-03-28) ---------------- diff --git a/TODO.md b/TODO.md index 1cab7b3f..4f556e43 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,6 @@ - raise an exception on .find/.find_all if the identifier given doesn't exists - .help() seems really slow on big piece of code (for example RedBaron("baron/grammator.py").read())("dict")[0].help() is suuuuuuuuuuuuuuuuper slow) - .at() return the first item starting at line X -- .rename() (name -> value, def/class -> name) - .replace() expect a whole valid python program. This could be fixed by look at "on_attribute" and resetting itself like that. - generate default constructors for nodes using nodes_rendering_order diff --git a/redbaron/base_nodes.py b/redbaron/base_nodes.py index 6222a1e0..0326b304 100644 --- a/redbaron/base_nodes.py +++ b/redbaron/base_nodes.py @@ -879,6 +879,7 @@ def _get_helpers(self): 'path', 'find_by_path', 'replace', + 'rename', 'edit', 'increase_indentation', 'decrease_indentation', @@ -1020,6 +1021,14 @@ def replace(self, new_node): self.__class__ = new_node.__class__ # YOLO self.__init__(new_node.fst(), parent=self.parent, on_attribute=self.on_attribute) + def rename(self, new_value): + if self.type in ('def', 'class'): + setattr(self, 'name', new_value) + elif self.type == 'name': + setattr(self, 'value', new_value) + else: + raise TypeError('Rename method does not support {0} type'.format(self.type)) + def edit(self, editor=None): if editor is None: editor = os.environ.get("EDITOR", "nano") diff --git a/tests/test_initial_parsing.py b/tests/test_initial_parsing.py index 055984d9..99141915 100644 --- a/tests/test_initial_parsing.py +++ b/tests/test_initial_parsing.py @@ -949,6 +949,22 @@ def test_replace(): assert red.dumps() == "caramba" +def test_rename(): + red = RedBaron('def foo(a):\n a = 5\n b = a') + red[0].rename('bar') + a = red.find_all('NameNode')[1] + a.rename('b') + red.find('DefArgumentNode').find('NameNode').rename('b') + red.find('NameNode', value='b').rename('q') + red.find_all('NameNode')[2].rename('q') + red.find_all('NameNode')[3].rename('b') + assert red.find('DefNode').name == 'bar' + assert red.find_all('NameNode')[0].value == 'q' + assert red.find_all('NameNode')[1].value == 'b' + assert red.find_all('NameNode')[2].value == 'q' + assert red.find_all('NameNode')[3].value == 'b' + + def test_insert_before(): red = RedBaron("a = 1\nprint(pouet)\n") red.print_.insert_before("chocolat") From 1a8f1f75c820ffb63a0445cad1b74b1a0982adfa Mon Sep 17 00:00:00 2001 From: bey Date: Thu, 11 Aug 2016 12:27:25 +0300 Subject: [PATCH 2/6] Update test case --- tests/test_initial_parsing.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_initial_parsing.py b/tests/test_initial_parsing.py index 99141915..d4d9c53a 100644 --- a/tests/test_initial_parsing.py +++ b/tests/test_initial_parsing.py @@ -952,16 +952,15 @@ def test_replace(): def test_rename(): red = RedBaron('def foo(a):\n a = 5\n b = a') red[0].rename('bar') + red.find('NameNode', value='b').rename('q') a = red.find_all('NameNode')[1] a.rename('b') red.find('DefArgumentNode').find('NameNode').rename('b') - red.find('NameNode', value='b').rename('q') - red.find_all('NameNode')[2].rename('q') red.find_all('NameNode')[3].rename('b') assert red.find('DefNode').name == 'bar' - assert red.find_all('NameNode')[0].value == 'q' - assert red.find_all('NameNode')[1].value == 'b' assert red.find_all('NameNode')[2].value == 'q' + assert red.find_all('NameNode')[0].value == 'b' + assert red.find_all('NameNode')[1].value == 'b' assert red.find_all('NameNode')[3].value == 'b' From 028f134751f8a3659b68de3d90c0b90dacbd43a1 Mon Sep 17 00:00:00 2001 From: bey Date: Thu, 11 Aug 2016 12:08:44 +0300 Subject: [PATCH 3/6] Add rename method with tests. Remove task from TODO list. Update CHANGELOG. --- CHANGELOG | 1 + tests/test_initial_parsing.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 4af804da..4929c138 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ Changelog - fix some old call to log() weren't lazy, that could cause a crash in some situations by an infinite recursive call and also reduce performances - fix in _iter_in_rendering_order method to avoid bug in edge cases (issue #107) +- add rename method for NameNode and def/class 0.6.1 (2016-03-28) ---------------- diff --git a/tests/test_initial_parsing.py b/tests/test_initial_parsing.py index acdc02b3..39013d54 100644 --- a/tests/test_initial_parsing.py +++ b/tests/test_initial_parsing.py @@ -994,6 +994,22 @@ def test_replace(): assert red.dumps() == "caramba" +def test_rename(): + red = RedBaron('def foo(a):\n a = 5\n b = a') + red[0].rename('bar') + a = red.find_all('NameNode')[1] + a.rename('b') + red.find('DefArgumentNode').find('NameNode').rename('b') + red.find('NameNode', value='b').rename('q') + red.find_all('NameNode')[2].rename('q') + red.find_all('NameNode')[3].rename('b') + assert red.find('DefNode').name == 'bar' + assert red.find_all('NameNode')[0].value == 'q' + assert red.find_all('NameNode')[1].value == 'b' + assert red.find_all('NameNode')[2].value == 'q' + assert red.find_all('NameNode')[3].value == 'b' + + def test_insert_before(): red = RedBaron("a = 1\nprint(pouet)\n") red.print_.insert_before("chocolat") From 910ed2325680ba1c216bba7f553288c75d11053f Mon Sep 17 00:00:00 2001 From: bey Date: Thu, 11 Aug 2016 12:27:25 +0300 Subject: [PATCH 4/6] Update test case --- tests/test_initial_parsing.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_initial_parsing.py b/tests/test_initial_parsing.py index 39013d54..55c79293 100644 --- a/tests/test_initial_parsing.py +++ b/tests/test_initial_parsing.py @@ -997,16 +997,15 @@ def test_replace(): def test_rename(): red = RedBaron('def foo(a):\n a = 5\n b = a') red[0].rename('bar') + red.find('NameNode', value='b').rename('q') a = red.find_all('NameNode')[1] a.rename('b') red.find('DefArgumentNode').find('NameNode').rename('b') - red.find('NameNode', value='b').rename('q') - red.find_all('NameNode')[2].rename('q') red.find_all('NameNode')[3].rename('b') assert red.find('DefNode').name == 'bar' - assert red.find_all('NameNode')[0].value == 'q' - assert red.find_all('NameNode')[1].value == 'b' assert red.find_all('NameNode')[2].value == 'q' + assert red.find_all('NameNode')[0].value == 'b' + assert red.find_all('NameNode')[1].value == 'b' assert red.find_all('NameNode')[3].value == 'b' From 53ed6377dbecc54068d19c9c7a3ffb66a9e90a51 Mon Sep 17 00:00:00 2001 From: Mehti Musayev Date: Sat, 24 Dec 2016 13:16:00 +0300 Subject: [PATCH 5/6] Merge with master branch --- CHANGELOG | 1 - tests/test_initial_parsing.py | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4929c138..4af804da 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,7 +13,6 @@ Changelog - fix some old call to log() weren't lazy, that could cause a crash in some situations by an infinite recursive call and also reduce performances - fix in _iter_in_rendering_order method to avoid bug in edge cases (issue #107) -- add rename method for NameNode and def/class 0.6.1 (2016-03-28) ---------------- diff --git a/tests/test_initial_parsing.py b/tests/test_initial_parsing.py index 55c79293..acdc02b3 100644 --- a/tests/test_initial_parsing.py +++ b/tests/test_initial_parsing.py @@ -994,21 +994,6 @@ def test_replace(): assert red.dumps() == "caramba" -def test_rename(): - red = RedBaron('def foo(a):\n a = 5\n b = a') - red[0].rename('bar') - red.find('NameNode', value='b').rename('q') - a = red.find_all('NameNode')[1] - a.rename('b') - red.find('DefArgumentNode').find('NameNode').rename('b') - red.find_all('NameNode')[3].rename('b') - assert red.find('DefNode').name == 'bar' - assert red.find_all('NameNode')[2].value == 'q' - assert red.find_all('NameNode')[0].value == 'b' - assert red.find_all('NameNode')[1].value == 'b' - assert red.find_all('NameNode')[3].value == 'b' - - def test_insert_before(): red = RedBaron("a = 1\nprint(pouet)\n") red.print_.insert_before("chocolat") From 4fff443da12f8fb653fa20ae222c319e6fbcc8ec Mon Sep 17 00:00:00 2001 From: Mehti Musayev Date: Sat, 24 Dec 2016 14:15:29 +0300 Subject: [PATCH 6/6] Fix merging bug. Add test for class. --- CHANGELOG | 2 +- TODO.md | 9 +- redbaron/base_nodes.py | 389 ++++++++++++++++++++-------------- tests/test_initial_parsing.py | 36 +++- 4 files changed, 257 insertions(+), 179 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4929c138..6738732d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,13 +7,13 @@ Changelog - fix help() after append - fix _synchronise() for base_nodes to avoid recursion in __repr__ function if code run not in the shell - add at method +- add rename method 0.6.2 (2016-10-03) ---------------- - fix some old call to log() weren't lazy, that could cause a crash in some situations by an infinite recursive call and also reduce performances - fix in _iter_in_rendering_order method to avoid bug in edge cases (issue #107) -- add rename method for NameNode and def/class 0.6.1 (2016-03-28) ---------------- diff --git a/TODO.md b/TODO.md index 9233fda4..860c9ef0 100644 --- a/TODO.md +++ b/TODO.md @@ -2,10 +2,7 @@ ### Important -- raise an exception on .find/.find_all if the identifier given doesn't exists - .help() seems really slow on big piece of code (for example RedBaron("baron/grammator.py").read())("dict")[0].help() is suuuuuuuuuuuuuuuuper slow) -- .at() return the first item starting at line X -- .rename() (name -> value, def/class -> name) - .replace() expect a whole valid python program. This could be fixed by look at "on_attribute" and resetting itself like that. - generate default constructors for nodes using nodes_rendering_order @@ -25,6 +22,12 @@ in addition of passing empty string, allow to pass None value on setattr this needs to be done in "_convert_input_to_node_object" and it's possible now since we have string type in nodes_rendering_order +- implement tree visitor and transformer like in standard ast: https://docs.python.org/3/library/ast.html#ast.NodeTransformer +- improve/create new insert method with inserting to specific position like find_by_position +- implement control-flow graph, data-flow-graph and call-graph +- add scope (RedBaron, ClassNode, DefNode, LambdaNode and GeneratorComprehensionNode) property +- check code for correctness before dumping + ### Find/Find\_All (comparison) Magic stuff like: diff --git a/redbaron/base_nodes.py b/redbaron/base_nodes.py index cb7c8d6d..e6133ac1 100644 --- a/redbaron/base_nodes.py +++ b/redbaron/base_nodes.py @@ -16,11 +16,11 @@ import redbaron -from redbaron.utils import redbaron_classname_to_baron_type, baron_type_to_redbaron_classname, log, in_a_shell, indent, truncate +from redbaron.utils import redbaron_classname_to_baron_type, baron_type_to_redbaron_classname, log, in_a_shell, indent, \ + truncate from redbaron.private_config import runned_from_ipython from redbaron.syntax_highlight import help_highlight, python_highlight, python_html_highlight - if python_version == 3: from collections import UserList else: @@ -81,7 +81,9 @@ def to_baron_path(self): return self.path def __str__(self): - return 'Path(%s @ %s)' % (self.node.__class__.__name__ + ('(' + self.node.type + ')' if isinstance(self.node, Node) else ''), str(self.path)) + return 'Path(%s @ %s)' % ( + self.node.__class__.__name__ + ('(' + self.node.type + ')' if isinstance(self.node, Node) else ''), + str(self.path)) def __repr__(self): return '<' + self.__str__() + ' object at ' + str(id(self)) + '>' @@ -116,14 +118,22 @@ def get_holder_on_attribute(class_, node): parent = parent.node_list if isinstance(parent, NodeList): - pos = parent.index(node.node_list if isinstance(node, ProxyList) else node) + if isinstance(node, ProxyList): + item = node.node_list + else: + item = node + pos = parent.index(item) return pos if isinstance(node, NodeList): - return next((key for (_, key, _) in parent._render() if getattr(parent, key, None) is node or getattr(getattr(parent, key, None), "node_list", None) is node), None) + return next((key for (_, key, _) in parent._render() if + getattr(parent, key, None) is node or getattr(getattr(parent, key, None), "node_list", + None) is node), None) to_return = next((key for (_, key, _) in parent._render() if key == node.on_attribute), None) return to_return + + class LiteralyEvaluable(object): def to_python(self): try: @@ -139,6 +149,7 @@ class GenericNodesUtils(object): """ Mixen top class for Node and NodeList that contains generic methods that are used by both. """ + def _convert_input_to_node_object(self, value, parent, on_attribute, generic=False): if isinstance(value, string_instance): if generic: @@ -162,7 +173,7 @@ def _convert_input_to_node_object_list(self, value, parent, on_attribute): return self._string_to_node_list(value, parent=parent, on_attribute=on_attribute) if isinstance(value, dict): # assuming that we got some fst - # also assuming the user do strange things + # also assuming the user do strange things return NodeList([Node.from_fst(value, parent=parent, on_attribute=on_attribute)]) if isinstance(value, Node): @@ -200,10 +211,27 @@ def absolute_bounding_box(self): def find_by_position(self, position): path = Path.from_baron_path(self, baron.path.position_to_path(self.fst(), position)) - if path: - return path.node - else: - return None + return path.node if path else None + + def at(self, line_no): + if not 0 <= line_no <= self.absolute_bounding_box.bottom_right.line: + raise IndexError("Line number {0} is outside of the file".format(line_no)) + node = self.find_by_position((line_no, 1)) + if node.absolute_bounding_box.top_left.line == line_no: + if hasattr(node.parent, 'absolute_bounding_box') and \ + node.parent.absolute_bounding_box.top_left.line == line_no and \ + node.parent.parent is not None: + return node.parent + return node + elif node is not None and hasattr(node, 'next_rendered'): + return list(self._iter_in_rendering_order(node.next_rendered))[0] + elif node.parent is None: + node = node.data[0][0] + while True: + if node.absolute_bounding_box.top_left.line == line_no: + return node + node = node.next_rendered + return node def _string_to_node_list(self, string, parent, on_attribute): return NodeList.from_fst(baron.parse(string), parent=parent, on_attribute=on_attribute) @@ -246,6 +274,8 @@ def _iter_in_rendering_order(self, node): if not (isinstance(node, Node) and node.type == "endl"): yield node for kind, key, display in node._render(): + if isinstance(display, string_instance) and not getattr(node, display): + continue if kind == "constant": yield node elif kind == "string": @@ -276,7 +306,8 @@ def __init__(self, initlist=None, parent=None, on_attribute=None): @classmethod def from_fst(klass, node_list, parent=None, on_attribute=None): - return klass(map(lambda x: Node.from_fst(x, parent=parent, on_attribute=on_attribute), node_list), parent=parent, on_attribute=on_attribute) + return klass(map(lambda x: Node.from_fst(x, parent=parent, on_attribute=on_attribute), node_list), + parent=parent, on_attribute=on_attribute) def find(self, identifier, *args, **kwargs): for i in self.data: @@ -286,18 +317,22 @@ def find(self, identifier, *args, **kwargs): def __getattr__(self, key): if key not in redbaron.ALL_IDENTIFIERS: - raise AttributeError("%s instance has no attribute '%s' and '%s' is not a valid identifier of another node" % (self.__class__.__name__, key, key)) + raise AttributeError( + "%s instance has no attribute '%s' and '%s' is not a valid identifier of another node" % ( + self.__class__.__name__, key, key)) return self.find(key) def __setitem__(self, key, value): self.data[key] = self._convert_input_to_node_object(value, parent=self.parent, on_attribute=self.on_attribute) + def find_iter(self, identifier, *args, **kwargs): + for node in self.data: + for matched_node in node.find_iter(identifier, *args, **kwargs): + yield matched_node + def find_all(self, identifier, *args, **kwargs): - to_return = NodeList([]) - for i in self.data: - to_return += i.find_all(identifier, *args, **kwargs) - return to_return + return NodeList(list(self.find_iter(identifier, *args, **kwargs))) findAll = find_all __call__ = find_all @@ -320,13 +355,13 @@ def __repr__(self): return self.__str__() return "<%s %s, \"%s\" %s, on %s %s>" % ( - self.__class__.__name__, - self.path().to_baron_path(), - truncate(self.dumps().replace("\n", "\\n"), 20), - id(self), - self.parent.__class__.__name__, - id(self.parent) - ) + self.__class__.__name__, + self.path().to_baron_path(), + truncate(self.dumps().replace("\n", "\\n"), 20), + id(self), + self.parent.__class__.__name__, + id(self.parent) + ) def __str__(self): to_return = "" @@ -334,7 +369,6 @@ def __str__(self): to_return += ("%-3s " % number) + "\n ".join(value.__repr__().split("\n")) to_return += "\n" return to_return - # return "%s" % [x.__repr__() for x in self.data] def _bytes_repr_html_(self): def __repr_html(self): @@ -351,6 +385,7 @@ def __repr_html(self): yield b"" yield b"" yield b"" + return b''.join(__repr_html(self)) def _repr_html_(self): @@ -373,14 +408,12 @@ def next_generator(self): # trick to return an empty generator # I wonder if I should not raise instead :/ return - yield def previous_generator(self): # similary, NodeList will never have next items # trick to return an empty generator # I wonder if I should not raise instead :/ return - yield def apply(self, function): [function(x) for x in self.data] @@ -393,7 +426,8 @@ def filter(self, function): return NodeList([x for x in self.data if function(x)]) def filtered(self): - return tuple([x for x in self.data if not isinstance(x, (redbaron.nodes.EndlNode, redbaron.nodes.CommaNode, redbaron.nodes.DotNode))]) + return tuple([x for x in self.data if + not isinstance(x, (redbaron.nodes.EndlNode, redbaron.nodes.CommaNode, redbaron.nodes.DotNode))]) def _generate_nodes_in_rendering_order(self): previous = None @@ -469,7 +503,6 @@ def from_fst(klass, node, parent=None, on_attribute=None): class_name = baron_type_to_redbaron_classname(node["type"]) return getattr(redbaron.nodes, class_name)(node, parent=parent, on_attribute=on_attribute) - @property def next(self): in_list = self._get_list_attribute_is_member_off() @@ -622,7 +655,10 @@ def _get_list_attribute_is_member_off(self): if self.on_attribute is "root": in_list = self.parent elif self.on_attribute is not None: - in_list = getattr(self.parent, self.on_attribute) + if isinstance(self.parent, NodeList): + in_list = getattr(self.parent.parent, self.on_attribute) + else: + in_list = getattr(self.parent, self.on_attribute) else: return None @@ -634,41 +670,6 @@ def _get_list_attribute_is_member_off(self): return in_list - - def find(self, identifier, *args, **kwargs): - if "recursive" in kwargs: - recursive = kwargs["recursive"] - kwargs = kwargs.copy() - del kwargs["recursive"] - else: - recursive = True - - if self._node_match_query(self, identifier, *args, **kwargs): - return self - - if not recursive: - return None - - for kind, key, _ in filter(lambda x: x[0] in ("list", "key"), self._render()): - if kind == "key": - i = getattr(self, key) - if not i: - continue - - found = i.find(identifier, *args, **kwargs) - if found is not None: - return found - - elif kind == "list": - attr = getattr(self, key).node_list if isinstance(getattr(self, key), ProxyList) else getattr(self, key) - for i in attr: - found = i.find(identifier, *args, **kwargs) - if found is not None: - return found - - else: - raise Exception() - def __getattr__(self, key): if key.endswith("_") and key[:-1] in self._dict_keys + self._list_keys + self._str_keys: return getattr(self, key[:-1]) @@ -677,7 +678,9 @@ def __getattr__(self, key): return getattr(self.value, key) if key not in redbaron.ALL_IDENTIFIERS: - raise AttributeError("%s instance has no attribute '%s' and '%s' is not a valid identifier of another node" % (self.__class__.__name__, key, key)) + raise AttributeError( + "%s instance has no attribute '%s' and '%s' is not a valid identifier of another node" % ( + self.__class__.__name__, key, key)) return self.find(key) @@ -727,8 +730,7 @@ def __delslice__(self, i, j): else: raise AttributeError("__delitem__") - def find_all(self, identifier, *args, **kwargs): - to_return = NodeList([]) + def find_iter(self, identifier, *args, **kwargs): if "recursive" in kwargs: recursive = kwargs["recursive"] kwargs = kwargs.copy() @@ -737,31 +739,29 @@ def find_all(self, identifier, *args, **kwargs): recursive = True if self._node_match_query(self, identifier, *args, **kwargs): - to_return.append(self) - - if not recursive: - return to_return - - for kind, key, _ in filter(lambda x: x[0] in ("list", "formatting") or (x[0] == "key" and isinstance(getattr(self, x[1]), Node)), self._render()): - if kind == "key": - i = getattr(self, key) - if not i: - continue + yield self + + if recursive: + for (kind, key, _) in self._render(): + if kind == "key": + node = getattr(self, key) + if not isinstance(node, Node): + continue + for matched_node in node.find_iter(identifier, *args, **kwargs): + yield matched_node + elif kind in ("list", "formatting"): + nodes = getattr(self, key) + if isinstance(nodes, ProxyList): + nodes = nodes.node_list + for node in nodes: + for matched_node in node.find_iter(identifier, *args, **kwargs): + yield matched_node - to_return += i.find_all(identifier, *args, **kwargs) - - elif kind in ("list", "formatting"): - if isinstance(getattr(self, key), ProxyList): - for i in getattr(self, key).node_list: - to_return += i.find_all(identifier, *args, **kwargs) - else: - for i in getattr(self, key): - to_return += i.find_all(identifier, *args, **kwargs) - - else: - raise Exception() + def find(self, identifier, *args, **kwargs): + return next(self.find_iter(identifier, *args, **kwargs), None) - return to_return + def find_all(self, identifier, *args, **kwargs): + return NodeList(list(self.find_iter(identifier, *args, **kwargs))) findAll = find_all __call__ = find_all @@ -776,7 +776,9 @@ def parent_find(self, identifier, *args, **kwargs): return None def _node_match_query(self, node, identifier, *args, **kwargs): - if not self._attribute_match_query(node.generate_identifiers(), identifier.lower() if isinstance(identifier, string_instance) and not identifier.startswith("re:") else identifier): + if not self._attribute_match_query(node.generate_identifiers(), identifier.lower() if isinstance(identifier, + string_instance) and not identifier.startswith( + "re:") else identifier): return False all_my_keys = node._str_keys + node._list_keys + node._dict_keys @@ -831,7 +833,6 @@ def _attribute_match_query(self, attribute_names, query): return False - def find_by_path(self, path): path = Path(self, path).node return path.node if path else None @@ -850,38 +851,50 @@ def generate_identifiers(klass): def _get_helpers(self): not_helpers = set([ + 'at', 'copy', + 'decrease_indentation', 'dumps', + 'edit', 'find', - 'findAll', 'find_all', - 'fst', - 'help', - 'next_generator', - 'previous_generator', - 'get_indentation_node', - 'indentation_node_is_direct', - 'parent_find', - 'path', + 'findAll', 'find_by_path', - 'replace', - 'edit', - 'increase_indentation', - 'decrease_indentation', - 'has_render_key', - 'get_absolute_bounding_box_of_attribute', 'find_by_position', - 'parse_code_block', - 'parse_decorators', + 'find_iter', 'from_fst', + 'fst', + 'fst', + 'generate_identifiers', + 'get_absolute_bounding_box_of_attribute', + 'get_indentation_node', + 'get_indentation_node', + 'has_render_key', + 'help', + 'help', + 'increase_indentation', + 'indentation_node_is_direct', + 'indentation_node_is_direct', 'index_on_parent', 'index_on_parent_raw', - 'insert_before', 'insert_after', + 'insert_before', + 'next_generator', + 'next_generator', + 'parent_find', + 'parent_find', + 'parse_code_block', + 'parse_decorators', + 'path', + 'path', + 'previous_generator', + 'previous_generator', + 'replace', + 'rename', 'to_python', - 'generate_identifiers', ]) - return [x for x in dir(self) if not x.startswith("_") and x not in not_helpers and inspect.ismethod(getattr(self, x))] + return [x for x in dir(self) if + not x.startswith("_") and x not in not_helpers and inspect.ismethod(getattr(self, x))] def fst(self): to_return = {} @@ -922,23 +935,33 @@ def __help__(self, deep=2, with_formatting=False): to_join.append("# helpers: %s" % ", ".join(self._get_helpers())) if self._default_test_value != "value": to_join.append("# default test value: %s" % self._default_test_value) - to_join += ["%s=%s" % (key, repr(getattr(self, key))) for key in self._str_keys if key != "type" and "formatting" not in key] - to_join += ["%s ->\n %s" % (key, indent(getattr(self, key).__help__(deep=new_deep, with_formatting=with_formatting), " ").lstrip() if getattr(self, key) else getattr(self, key)) for key in self._dict_keys if "formatting" not in key] + to_join += ["%s=%s" % (key, repr(getattr(self, key))) for key in self._str_keys if + key != "type" and "formatting" not in key] + to_join += ["%s ->\n %s" % (key, indent( + getattr(self, key).__help__(deep=new_deep, with_formatting=with_formatting), + " ").lstrip() if getattr(self, key) else getattr(self, key)) for key in self._dict_keys if + "formatting" not in key] # need to do this otherwise I end up with stacked quoted list # example: value=[\'DottedAsNameNode(target=\\\'None\\\', as=\\\'False\\\', value=DottedNameNode(value=["NameNode(value=\\\'pouet\\\')"])] for key in filter(lambda x: "formatting" not in x, self._list_keys): to_join.append(("%s ->" % key)) for i in getattr(self, key): - to_join.append(" * " + indent(i.__help__(deep=new_deep, with_formatting=with_formatting), " ").lstrip()) + to_join.append( + " * " + indent(i.__help__(deep=new_deep, with_formatting=with_formatting), " ").lstrip()) if deep and with_formatting: - to_join += ["%s=%s" % (key, repr(getattr(self, key))) for key in self._str_keys if key != "type" and "formatting" in key] - to_join += ["%s=%s" % (key, getattr(self, key).__help__(deep=new_deep, with_formatting=with_formatting) if getattr(self, key) else getattr(self, key)) for key in self._dict_keys if "formatting" in key] + to_join += ["%s=%s" % (key, repr(getattr(self, key))) for key in self._str_keys if + key != "type" and "formatting" in key] + to_join += ["%s=%s" % (key, getattr(self, key).__help__(deep=new_deep, + with_formatting=with_formatting) if getattr(self, + key) else getattr( + self, key)) for key in self._dict_keys if "formatting" in key] for key in filter(lambda x: "formatting" in x, self._list_keys): to_join.append(("%s ->" % key)) for i in getattr(self, key): - to_join.append(" * " + indent(i.__help__(deep=new_deep, with_formatting=with_formatting), " ").lstrip()) + to_join.append( + " * " + indent(i.__help__(deep=new_deep, with_formatting=with_formatting), " ").lstrip()) return "\n ".join(to_join) @@ -947,13 +970,13 @@ def __repr__(self): return self.__str__() return "<%s path=%s, \"%s\" %s, on %s %s>" % ( - self.__class__.__name__, - self.path().to_baron_path(), - truncate(self.dumps().replace("\n", "\\n"), 20), - id(self), - self.parent.__class__.__name__, - id(self.parent) - ) + self.__class__.__name__, + self.path().to_baron_path(), + truncate(self.dumps().replace("\n", "\\n"), 20), + id(self), + self.parent.__class__.__name__, + id(self.parent) + ) def __str__(self): if runned_from_ipython(): @@ -987,7 +1010,6 @@ def __setattr__(self, name, value): return super(Node, self).__setattr__(name, value) - def _render(self): return nodes_rendering_order[self.type] @@ -996,6 +1018,14 @@ def replace(self, new_node): self.__class__ = new_node.__class__ # YOLO self.__init__(new_node.fst(), parent=self.parent, on_attribute=self.on_attribute) + def rename(self, new_value): + if self.type in ('def', 'class'): + self.name = new_value + elif self.type == 'name': + self.value = new_value + else: + raise TypeError('Rename method does not support {0} type'.format(self.type)) + def edit(self, editor=None): if editor is None: editor = os.environ.get("EDITOR", "nano") @@ -1107,7 +1137,8 @@ def parse_code_block(self, string, parent, on_attribute): elif indentation < target_indentation: result.increase_indentation(target_indentation - indentation) - endl_base_node = Node.from_fst({'formatting': [], 'indent': '', 'type': 'endl', 'value': '\n'}, on_attribute=on_attribute, parent=parent) + endl_base_node = Node.from_fst({'formatting': [], 'indent': '', 'type': 'endl', 'value': '\n'}, + on_attribute=on_attribute, parent=parent) if (self.on_attribute == "root" and self.next) or (not self.next and self.parent and self.parent.next): # I need to finish with 3 endl nodes @@ -1179,9 +1210,13 @@ def remove_trailing_endl(node): last_member = self remove_trailing_endl(last_member) if isinstance(last_member.value, ProxyList): - last_member.value.node_list.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=last_member, on_attribute="value")) + last_member.value.node_list.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, + parent=last_member, on_attribute="value")) else: - last_member.value.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=last_member, on_attribute="value")) + last_member.value.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, + parent=last_member, on_attribute="value")) return "" if re.match("^\s*%s" % indented_type, string): @@ -1193,7 +1228,8 @@ def remove_trailing_endl(node): string = re.sub("(\r?\n)%s" % (" " * indentation), "\\1", string) string = string.lstrip() - node = Node.from_fst(baron.parse("try: pass\nexcept: pass\n%s" % string)[0][indented_type], parent=parent, on_attribute=on_attribute) + node = Node.from_fst(baron.parse("try: pass\nexcept: pass\n%s" % string)[0][indented_type], parent=parent, + on_attribute=on_attribute) node.value = self.parse_code_block(node.value.dumps(), parent=node, on_attribute="value") else: @@ -1206,14 +1242,16 @@ def remove_trailing_endl(node): 'indent': '', 'type': 'endl', 'value': '\n'}] - } + } node = Node.from_fst(fst, parent=parent, on_attribute=on_attribute) node.value = self.parse_code_block(string=string, parent=parent, on_attribute=on_attribute) # ensure that the node ends with only one endl token, we'll add more later if needed remove_trailing_endl(node) - node.value.node_list.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=node, on_attribute="value")) + node.value.node_list.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=node, + on_attribute="value")) last_member = self._get_last_member_to_clean() @@ -1221,15 +1259,25 @@ def remove_trailing_endl(node): if self.next: remove_trailing_endl(last_member) if isinstance(last_member.value, ProxyList): - last_member.value.node_list.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=last_member, on_attribute="value")) + last_member.value.node_list.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, + parent=last_member, on_attribute="value")) else: - last_member.value.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=last_member, on_attribute="value")) + last_member.value.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, + parent=last_member, on_attribute="value")) if self.indentation: - node.value.node_list.append(redbaron.nodes.EndlNode({"type": "endl", "indent": self.indentation, "formatting": [], "value": "\n"}, parent=node, on_attribute="value")) + node.value.node_list.append(redbaron.nodes.EndlNode( + {"type": "endl", "indent": self.indentation, "formatting": [], "value": "\n"}, parent=node, + on_attribute="value")) else: # we are on root level and followed: we need 2 blanks lines after the node - node.value.node_list.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=node, on_attribute="value")) - node.value.node_list.append(redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, parent=node, on_attribute="value")) + node.value.node_list.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, + parent=node, on_attribute="value")) + node.value.node_list.append( + redbaron.nodes.EndlNode({"type": "endl", "indent": "", "formatting": [], "value": "\n"}, + parent=node, on_attribute="value")) if isinstance(last_member.value, ProxyList): last_member.value.node_list[-1].indent = self.indentation @@ -1256,7 +1304,8 @@ def __init__(self, node_list, on_attribute="value"): self.node_list = node_list self.heading_formatting = [] self.data = self._build_inner_list(node_list) - self.middle_separator = redbaron.nodes.CommaNode({"type": "comma", "first_formatting": [], "second_formatting": [{"type": "space", "value": " "}]}) + self.middle_separator = redbaron.nodes.CommaNode( + {"type": "comma", "first_formatting": [], "second_formatting": [{"type": "space", "value": " "}]}) self.on_attribute = on_attribute def _build_inner_list(self, node_list): @@ -1339,7 +1388,8 @@ def append(self, value): self.insert(len(self), value) def extend(self, values): - self.data.extend(map(lambda x: [x, None], self._convert_input_to_node_object_list(values, parent=self.node_list, on_attribute=self.on_attribute))) + self.data.extend(map(lambda x: [x, None], self._convert_input_to_node_object_list(values, parent=self.node_list, + on_attribute=self.on_attribute))) self._synchronise() def pop(self, index=None): @@ -1381,11 +1431,13 @@ def __setitem__(self, key, value): if isinstance(key, slice): self.__setslice__(key.start, key.stop, value) else: - self.data[key][0] = self._convert_input_to_node_object(value, parent=self.node_list, on_attribute=self.on_attribute) + self.data[key][0] = self._convert_input_to_node_object(value, parent=self.node_list, + on_attribute=self.on_attribute) self._synchronise() def __setslice__(self, i, j, value): - self.data[i:j] = map(lambda x: [x, None], self._convert_input_to_node_object_list(value, parent=self.node_list, on_attribute=self.on_attribute)) + self.data[i:j] = map(lambda x: [x, None], self._convert_input_to_node_object_list(value, parent=self.node_list, + on_attribute=self.on_attribute)) self._synchronise() def __delslice__(self, i, j): @@ -1401,13 +1453,13 @@ def __repr__(self): return self.__str__() return "<%s %s, \"%s\" %s, on %s %s>" % ( - self.__class__.__name__, - self.path().to_baron_path(), - truncate(self.dumps().replace("\n", "\\n"), 20), - id(self), - self.parent.__class__.__name__, - id(self.parent) - ) + self.__class__.__name__, + self.path().to_baron_path(), + truncate(self.dumps().replace("\n", "\\n"), 20), + id(self), + self.parent.__class__.__name__, + id(self.parent) + ) def _bytes_repr_html_(self): def __repr_html(self): @@ -1424,6 +1476,7 @@ def __repr_html(self): yield b"" yield b"" yield b"" + return b''.join(__repr_html(self)) def _repr_html_(self): @@ -1452,9 +1505,11 @@ def __init__(self, node_list, on_attribute="value"): def _get_middle_separator(self): if self.style == "indented": - return redbaron.nodes.CommaNode({"type": "comma", "first_formatting": [], "second_formatting": [{"type": "endl", "indent": self.parent.indentation + " ", "formatting": [], "value": "\n"}]}) + return redbaron.nodes.CommaNode({"type": "comma", "first_formatting": [], "second_formatting": [ + {"type": "endl", "indent": self.parent.indentation + " ", "formatting": [], "value": "\n"}]}) - return redbaron.nodes.CommaNode({"type": "comma", "first_formatting": [], "second_formatting": [{"type": "space", "value": " "}]}) + return redbaron.nodes.CommaNode( + {"type": "comma", "first_formatting": [], "second_formatting": [{"type": "space", "value": " "}]}) def _generate_expected_list(self): def generate_separator(): @@ -1492,9 +1547,10 @@ def generate_separator(): # XXX will break comments if self.style == "indented": if not expected_list[-1].second_formatting.endl: - raise Exception("It appears that you have indentation in your CommaList, for now RedBaron doesn't know how to handle this situation (which requires a lot of work), sorry about that. You can find more information here https://github.com/PyCQA/redbaron/issues/100") - elif expected_list[-1].second_formatting.endl.indent != self.parent.indentation + " "*4: - expected_list[-1].second_formatting.endl.indent = self.parent.indentation + " "*4 + raise Exception( + "It appears that you have indentation in your CommaList, for now RedBaron doesn't know how to handle this situation (which requires a lot of work), sorry about that. You can find more information here https://github.com/PyCQA/redbaron/issues/100") + elif expected_list[-1].second_formatting.endl.indent != self.parent.indentation + " " * 4: + expected_list[-1].second_formatting.endl.indent = self.parent.indentation + " " * 4 else: # here we generate the new expected formatting # None is used as a sentry value for newly inserted values in the proxy list @@ -1506,11 +1562,11 @@ def generate_separator(): if expected_list and self.has_trailing and self.style == "indented": if not expected_list[-1].second_formatting.endl: - raise Exception("It appears that you have indentation in your CommaList, for now RedBaron doesn't know how to handle this situation (which requires a lot of work), sorry about that. You can find more information here https://github.com/PyCQA/redbaron/issues/100") + raise Exception( + "It appears that you have indentation in your CommaList, for now RedBaron doesn't know how to handle this situation (which requires a lot of work), sorry about that. You can find more information here https://github.com/PyCQA/redbaron/issues/100") elif expected_list[-1].second_formatting.endl.indent != self.parent.indentation: expected_list[-1].second_formatting.endl.indent = self.parent.indentation - return expected_list @@ -1572,6 +1628,7 @@ def _generate_expected_list(self): expected_list.append(separator) return expected_list + def _convert_input_to_node_object(self, value, parent, on_attribute): if value.startswith(("(", "[")): value = "a%s" % value @@ -1585,13 +1642,13 @@ class LineProxyList(ProxyList): def __init__(self, node_list, on_attribute="value"): self.first_blank_lines = [] super(LineProxyList, self).__init__(node_list, on_attribute=on_attribute) - self.middle_separator = redbaron.nodes.DotNode({"type": "endl", "formatting": [], "value": "\n", "indent": " "}) - + self.middle_separator = redbaron.nodes.DotNode( + {"type": "endl", "formatting": [], "value": "\n", "indent": " "}) def _synchronise(self): - log("Before synchronise, self.data = '%s' + '%s'", self.first_blank_lines, self.data) + log("Before synchronise, self.data = '%s' + '%s'", self.first_blank_lines, self.node_list) super(LineProxyList, self)._synchronise() - log("After synchronise, self.data = '%s' + '%s'", self.first_blank_lines, self.data) + log("After synchronise, self.data = '%s' + '%s'", self.first_blank_lines, self.node_list) def _build_inner_list(self, node_list): result = [] @@ -1625,7 +1682,8 @@ def _build_inner_list(self, node_list): return result def _get_separator_indentation(self): - return self.node_list.filtered()[0].indentation if self.node_list.filtered() else self.parent.indentation + " " + return self.node_list.filtered()[ + 0].indentation if self.node_list.filtered() else self.parent.indentation + " " def _generate_expected_list(self): log("Start _generate_expected_list for LineProxyList") @@ -1671,7 +1729,9 @@ def modify_last_indentation(node, indentation): for position, i in enumerate(self.data): log("[%s] %s", position, i) - if might_need_separator and i[0].type != "endl" and (not previous or previous.type != "endl") and not isinstance(previous, (CodeBlockNode, redbaron.nodes.IfelseblockNode)): + if might_need_separator and i[0].type != "endl" and ( + not previous or previous.type != "endl") and not isinstance(previous, ( + CodeBlockNode, redbaron.nodes.IfelseblockNode)): log(">> Previous line has content and current needs to be indented, append separator to indent it") expected_list.append(generate_separator()) log("-- current result: %s", ["".join(map(lambda x: x.dumps(), expected_list))]) @@ -1703,7 +1763,6 @@ def modify_last_indentation(node, indentation): log("Previous is CodeBlockNode and current isn't endl, ensure previous has the current identation") modify_last_indentation(get_real_last(previous.value), indentation) - # XXX this will need refactoring... if i[1] is not None: log("current doesn't have None for formatting") @@ -1711,7 +1770,8 @@ def modify_last_indentation(node, indentation): # to separate between the intems but has not so we add it # this happen because a new value has been added after this one if not is_last and not i[1] and not isinstance(i[0], CodeBlockNode): - log("If current isn't a CodeBlockNode and doesn't have a separator and isn't the last, mark it has might needing a separator") + log( + "If current isn't a CodeBlockNode and doesn't have a separator and isn't the last, mark it has might needing a separator") might_need_separator = True # XXX shoud uniformise the list of formatting nodes @@ -1755,7 +1815,8 @@ def modify_last_indentation(node, indentation): last_indentation = "" if not expected_list or not isinstance(expected_list[-1], (CodeBlockNode, redbaron.nodes.EndlNode)): - log(">> List is empty or last node is not a CodeBlockNode or EndlNode, append a separator to it and set identation to it") + log( + ">> List is empty or last node is not a CodeBlockNode or EndlNode, append a separator to it and set identation to it") expected_list.append(generate_separator()) expected_list[-1].indent = last_indentation log("-- current result: %s", ["".join(map(lambda x: x.dumps(), expected_list))]) diff --git a/tests/test_initial_parsing.py b/tests/test_initial_parsing.py index 55c79293..5139bee3 100644 --- a/tests/test_initial_parsing.py +++ b/tests/test_initial_parsing.py @@ -995,18 +995,32 @@ def test_replace(): def test_rename(): - red = RedBaron('def foo(a):\n a = 5\n b = a') - red[0].rename('bar') - red.find('NameNode', value='b').rename('q') - a = red.find_all('NameNode')[1] + red_def = RedBaron('def foo(a):\n a = 5\n b = a') + red_def[0].rename('bar') + a = red_def.find_all('NameNode')[1] a.rename('b') - red.find('DefArgumentNode').find('NameNode').rename('b') - red.find_all('NameNode')[3].rename('b') - assert red.find('DefNode').name == 'bar' - assert red.find_all('NameNode')[2].value == 'q' - assert red.find_all('NameNode')[0].value == 'b' - assert red.find_all('NameNode')[1].value == 'b' - assert red.find_all('NameNode')[3].value == 'b' + red_def.find('DefArgumentNode').find('NameNode').rename('b') + red_def.find('NameNode', value='b').rename('q') + red_def.find_all('NameNode')[2].rename('q') + red_def.find_all('NameNode')[3].rename('b') + + red_class = RedBaron('class Baz(q):\n h = 10\n m = 42') + red_class[0].rename('baz') + h = red_class.find_all('NameNode')[1] + h.rename('v') + red_class.find_all('NameNode')[0].rename('z') + red_class.find('NameNode', value='m').rename('p') + + assert red_def.find('DefNode').name == 'bar' + assert red_def.find_all('NameNode')[0].value == 'q' + assert red_def.find_all('NameNode')[1].value == 'b' + assert red_def.find_all('NameNode')[2].value == 'q' + assert red_def.find_all('NameNode')[3].value == 'b' + + assert red_class.find('ClassNode').name == 'baz' + assert red_class.find_all('NameNode')[0].value == 'z' + assert red_class.find_all('NameNode')[1].value == 'v' + assert red_class.find_all('NameNode')[2].value == 'p' def test_insert_before():