Other¶
List of other features of RedBaron.
.parent¶
Every node and node list have a .parent
attribute that points to the
parent node or node list. If the node doesn’t have a parent node (for example
the node list returned when constructing a new instance using the
RedBaron
class), the parent
attribute is set at None
. A
new node or node list created using .copy()
always have its
parent
attribute set at None
.
The attribute on which the node is assigned on the parent node is store in the
on_attribute
attribute. on_attribute
is set at "root"
if the parent is a RedBaron instance.
In [1]: red = RedBaron("a = 1 + caramba")
In [2]: red.help()
0 -----------------------------------------------------
AssignmentNode()
# identifiers: assign, assignment, assignment_, assignmentnode
operator=''
target ->
NameNode()
# identifiers: name, name_, namenode
value='a'
annotation ->
None
value ->
BinaryOperatorNode()
# identifiers: binary_operator, binary_operator_, binaryoperator, binaryoperatornode
value='+'
first ->
IntNode() ...
second ->
NameNode() ...
In [3]: red.parent
In [4]: red.on_attribute
In [5]: red[0].parent
Out[5]: 0 a = 1 + caramba
In [6]: red[0].on_attribute
Out[6]: 'root'
In [7]: red[0].target.parent
Out[7]: a = 1 + caramba
In [8]: red[0].target.on_attribute
Out[8]: 'target'
In [9]: red[0].value.parent
Out[9]: a = 1 + caramba
In [10]: red[0].value.on_attribute
Out[10]: 'value'
In [11]: red[0].value.first.parent
Out[11]: 1 + caramba
In [12]: red[0].value.first.on_attribute
Out[12]: 'first'
.parent_find()¶
A helper method that allow you to do the equivalent of the .find()
method but in the chain of the parents of the node. This is the equivalent of
doing: while node has a parent: if node.parent match query: return
node.parent, else: node = node.parent
. It returns None
if no parent
match the query.
In [13]: red = RedBaron("def a():\n with b:\n def c():\n pass")
In [14]: red.help()
0 -----------------------------------------------------
DefNode()
# identifiers: def, def_, defnode, funcdef, funcdef_
# default test value: name
async=False
name='a'
return_annotation ->
None
decorators ->
arguments ->
value ->
* WithNode()
# identifiers: with, with_, withnode
async=False
contexts ->
* WithContextItemNode() ...
value ->
* DefNode() ...
In [15]: r = red.pass_
In [16]: r
Out[16]: pass
In [17]: r.parent
Out[17]:
def c():
pass
In [18]: r.parent_find('def')
Out[18]:
def c():
pass
In [19]: r.parent_find('def', name='a')
Out[19]:
def a():
with b:
def c():
pass
In [20]: r.parent_find('def', name='dont_exist')
.next .previous .next_recursive .previous_recursive .next_generator() .previous_generator()¶
In a similar fashion, nodes have a .next
and .previous
attributes that point to the next or previous node if the node is located in a
node list. They are set at None
if there is not adjacent node or if the
node is not in a node list. A node list will never have a .next
or
.previous
node, so those attributes will always be set at None
.
Nodes also have a .next_generator()
and .previous_generator()
if you want to iterate on the neighbours of the node.
Nodes have also a .next_recursive
and
.previous_recursive
attribute. It is similar to the non
recursive function but differ in the fact that, when using
.next_recursive
on a node at the end of the list, it points to
the first adjacent node that exist in the parent hierarchy.
In [21]: red = RedBaron("[1, 2, 3]; a = 1")
In [22]: list = red[0]
In [23]: print(list.next)
;
In [24]: list.help()
ListNode()
# identifiers: list, list_, listnode
value ->
* IntNode()
# identifiers: int, int_, intnode
value='1'
* IntNode()
# identifiers: int, int_, intnode
value='2'
* IntNode()
# identifiers: int, int_, intnode
value='3'
In [25]: assign = red[2]
In [26]: assign.help()
AssignmentNode()
# identifiers: assign, assignment, assignment_, assignmentnode
operator=''
target ->
NameNode()
# identifiers: name, name_, namenode
value='a'
annotation ->
None
value ->
IntNode()
# identifiers: int, int_, intnode
value='1'
In [27]: list.value[2].help(deep=1)
IntNode()
# identifiers: int, int_, intnode
value='3'
.next_intuitive/.previous_intuitive¶
Due to its tree nature, navigating in the FST might not behave as the user
expect it. For example: doing a .next
on a TryNode
will not
return the first ExceptNode
(or FinallyNode
) but will return
the node after the try-excepts-else-finally node because it is a full node in
itself in the FST.
See for yourself:
In [28]: red = RedBaron("try:\n pass\nexcept:\n pass\nafter")
In [29]: red.try_
Out[29]:
try:
pass
except:
pass
In [30]: red.try_.next
Out[30]: after
In [31]: red.help()
0 -----------------------------------------------------
TryNode()
# identifiers: try, try_, trynode
else ->
None
finally ->
None
value ->
* PassNode()
# identifiers: pass, pass_, passnode
excepts ->
* ExceptNode()
# identifiers: except, except_, exceptnode
delimiter=''
exception ->
None
target ->
None
value ->
* PassNode() ...
1 -----------------------------------------------------
NameNode()
# identifiers: name, name_, namenode
value='after'
To solve this issue .next_intuitive
and .previous_intuitive
have been introduced:
In [32]: red
Out[32]:
0 try:
pass
except:
pass
1 after
In [33]: red.try_.next_intuitive
Out[33]:
except:
pass
In [34]: red.try_.next_intuitive.next_intuitive
Out[34]: after
This also applies to IfNode
, ElifNode
, ElseNode
,
ForNode
and WhileNode
(both of the last one can have an else
statement). This also works coming from nodes outsides of those previous nodes.
For IfNode
, ElifNode
and ElseNode
inside an
IfelseblockNode
:
In [35]: red = RedBaron("before\nif a:\n pass\nelif b:\n pass\nelse:\n pass\nafter")
In [36]: red
Out[36]:
0 before
1 if a:
pass
elif b:
pass
else:
pass
2 after
In [37]: red[1].help()
IfelseblockNode()
# identifiers: ifelseblock, ifelseblock_, ifelseblocknode
value ->
* IfNode()
# identifiers: if, if_, ifnode
test ->
NameNode() ...
value ->
* PassNode() ...
* ElifNode()
# identifiers: elif, elif_, elifnode
test ->
NameNode() ...
value ->
* PassNode() ...
* ElseNode()
# identifiers: else, else_, elsenode
value ->
* PassNode() ...
In [38]: red[1]
Out[38]:
if a:
pass
elif b:
pass
else:
pass
In [39]: red.if_.next
Out[39]:
elif b:
pass
In [40]: red.if_.next_intuitive
Out[40]:
elif b:
pass
In [41]: red.if_.next_intuitive.next_intuitive
Out[41]:
else:
pass
In [42]: red.if_.next_intuitive.next_intuitive.next_intuitive
Out[42]: after
In [43]: red.if_.next_intuitive.next_intuitive.next_intuitive.next_intuitive
Warning
There is a subtlety: IfelseblockNode
is unaffected by this
behavior: you have to use next_intuitive
or
previous_intuitive
on IfNode
, ElifNode
and
ElseNode
inside IfelseblockNode.
But, if you do a next_intuitive
or previous_intuitive
or a
node around IfelseblockNode
it will jump to the first or last node
inside the IfelseblockNode
.
See this example
In [44]: red = RedBaron("before\nif a:\n pass\nelif b:\n pass\nelse:\n pass\nafter")
In [45]: red[1].ifelseblock.next_intuitive # similar to .next
Out[45]: after
In [46]: red[1].ifelseblock.next.previous # this is the IfelseblockNode
Out[46]:
if a:
pass
elif b:
pass
else:
pass
In [47]: red[1].ifelseblock.next.previous_intuitive # this is the ElseNode
Out[47]:
else:
pass
In [48]: red[1].ifelseblock.previous.next # this is the IfelseblockNode
Out[48]:
if a:
pass
elif b:
pass
else:
pass
In [49]: red[1].ifelseblock.previous.next_intuitive # this is the IfNode
Out[49]:
if a:
pass
For ForNode
:
In [50]: red = RedBaron("for a in b:\n pass\nelse:\n pass\nafter")
In [51]: red
Out[51]:
0 for a in b:
pass
else:
pass
1 after
In [52]: red[0].help()
ForNode()
# identifiers: for, for_, fornode
async=False
iterator ->
NameNode()
# identifiers: name, name_, namenode
value='a'
target ->
NameNode()
# identifiers: name, name_, namenode
value='b'
else ->
ElseNode()
# identifiers: else, else_, elsenode
value ->
* PassNode() ...
value ->
* PassNode()
# identifiers: pass, pass_, passnode
In [53]: red.for_
Out[53]:
for a in b:
pass
else:
pass
In [54]: red.for_.next
Out[54]: after
In [55]: red.for_.next_intuitive
Out[55]:
else:
pass
In [56]: red.for_.next_intuitive.next_intuitive
Out[56]: after
For WhileNode
:
In [57]: red = RedBaron("while a:\n pass\nelse:\n pass\nafter")
In [58]: red
Out[58]:
0 while a:
pass
else:
pass
1 after
In [59]: red[0].help()
WhileNode()
# identifiers: while, while_, whilenode
test ->
NameNode()
# identifiers: name, name_, namenode
value='a'
else ->
ElseNode()
# identifiers: else, else_, elsenode
value ->
* PassNode() ...
value ->
* PassNode()
# identifiers: pass, pass_, passnode
In [60]: red.while_
Out[60]:
while a:
pass
else:
pass
In [61]: red.while_.next
Out[61]: after
In [62]: red.while_.next_intuitive
Out[62]:
else:
pass
In [63]: red.while_.next_intuitive.next_intuitive
Out[63]: after
.root¶
Every node have the .root
attribute (property) that returns the root
node in which this node is located:
In [64]: red = RedBaron("def a(): return 42")
In [65]: red.int_
Out[65]: 42
In [66]: assert red.int_.root is red
.index_on_parent¶
Every node have the .index_on_parent
attribute (property) that returns the index
at which this node is store in its parent node list. If the node isn’t stored
in a node list, it returns None
. If the node is stored in a proxy list
(Proxy List), it’s the index in the proxy list that is returned. to get
the unproxified index use .index_on_parent_raw.
In [67]: red = RedBaron("a = [1, 2, 3]")
In [68]: red[0].value.value
Out[68]:
0 1
1 2
2 3
In [69]: red[0].value.value[2]
Out[69]: 3
In [70]: red[0].value.value[2].index_on_parent
Out[70]: 2
In [71]: red[0].value
Out[71]: [1, 2, 3]
In [72]: red[0].value.index_on_parent
.index_on_parent_raw¶
Same as .index_on_parent except that it always return the unproxified whether the node is stored in a proxy list or not.
In [73]: red = RedBaron("a = [1, 2, 3]")
In [74]: red[0].value.value.node_list
Out[74]:
0 1
1 ,
2 2
3 ,
4 3
In [75]: red[0].value.value.node_list[2]
Out[75]: 2
In [76]: red[0].value.value.node_list[2].index_on_parent_raw
Out[76]: 2
.filtered()¶
Node list comes with a small helper function: .filtered()
that returns
a tuple containing the “significant” node (nodes that aren’t comma node, dot
node, space node or endl node).
In [77]: red = RedBaron("[1, 2, 3]")
In [78]: red[0].value
Out[78]:
0 1
1 2
2 3
In [79]: red[0].value.filtered()
Out[79]: (1, 2, 3)
Note: the fact that it’s a tuple that is returned will probably evolve in the future into a node list proxy or something like that, I just don’t have the time to do something better right now.
.indentation¶
Every node has the property .indentation
that will return the
indentation level of the node:
In [80]: red = RedBaron("while a:\n pass")
In [81]: red[0].indentation
Out[81]: ''
In [82]: red[0].test.indentation
Out[82]: ''
In [83]: red.pass_.indentation
Out[83]: ' '
In [84]: red = RedBaron("while a: pass")
In [85]: red.pass_.indentation
Out[85]: ''
.increase_indentation() and .decrease_indentation()¶
Those 2 methods allow you to change the indentation of a part of the tree. They expect the number of spaces to add or to remove as first argument.
In [86]: red = RedBaron("def a():\n if plop:\n pass")
In [87]: red
Out[87]:
0 def a():
if plop:
pass
In [88]: red[0].value.increase_indentation(15)
In [89]: red
Out[89]:
0 def a():
if plop:
pass
In [90]: red[0].value.decrease_indentation(15)
In [91]: red
Out[91]:
0 def a():
if plop:
pass
.to_python()¶
Warning
Since RedBaron calls ast.literal_eval it can only parse the python code parsed by the python version you are using.
For example if you are using a python version inferior to 3.6, to_python will crash on 100_000 because it is only supported since python 3.6
This method safely evaluate the current selected nodes. It wraps
ast.literal_eval, therefor, and
for security reasons, it only works on a subset of python: numbers, strings,
lists, dicts, tuples, boolean and None
. Of course, using this method on
a list/dict/tuple containing values that aren’t listed here will raise a
ValueError
.
In [92]: RedBaron("42")[0].value # string
Out[92]: '42'
In [93]: RedBaron("42")[0].to_python() # python int
Out[93]: 42
In [94]: RedBaron("'a' 'b'")[0].dumps()
Out[94]: "'a' 'b'"
In [95]: RedBaron("'a' 'b'")[0].to_python()
Out[95]: 'ab'
In [96]: RedBaron("u'unicode string'")[0].to_python()
Out[96]: u'unicode string'
In [97]: RedBaron("[1, 2, 3]")[0].to_python()
Out[97]: [1, 2, 3]
In [98]: RedBaron("(1, 2, 3)")[0].to_python()
Out[98]: (1, 2, 3)
In [99]: RedBaron("{'foo': 'bar'}")[0].to_python()
Out[99]: {'foo': 'bar'}
In [100]: RedBaron("False")[0].to_python()
Out[100]: False
In [101]: RedBaron("True")[0].to_python()
Out[101]: True
In [102]: print(RedBaron("None")[0].to_python())
None
.path()¶
Every node has a path()
method that will return a Path
object
to it. Every path object has a .node
attribute that point to the node
and a .to_baron_path
that returns a Baron Path namedtuple.
In [103]: red = RedBaron("while a:\n pass")
In [104]: red.pass_
Out[104]: pass
In [105]: path = red.pass_.path()
In [106]: path
Out[106]: <Path(PassNode(pass) @ [0, 'value', 1]) object at 140380385795984>
In [107]: path.node
Out[107]: pass
In [108]: path.to_baron_path()
Out[108]: [0, 'value', 1]
Path class¶
RedBaron provides a Path class that represent a path to a node.
-
class
redbaron.
Path
(node)¶ Holds the path to a FST node
Path(node): path coming from the node’s root Path.from_baron_path(node, path): path going down the node following the given path
Note that the second argument “path” is a baron path, i.e. list of keys that can be given for example by redbaron.Path(node).to_baron_path()
The second form is useful when converting a path given by baron to a redbaron node
.map .filter .apply¶
RedBaron nodes list have 3 helper methods .map
, .filter
and .apply
quite similar to python builtins (except for apply). The main difference is that they return a node list instance instead of a python buildin list.
.map
takes a callable (like a lambda or a function) that receive a node as first argument, this callable is applied on every node of the node list and a node list containing the return of those applies will be returned..filter
works like.map
but instead of returning a node list of the return of the callable, it returns a node list that contains the nodes for which the callable returnedTrue
(or something consideredTrue
in python).apply
works like.map
but instead of returning the result of the callable, it returns to original node.
In [109]: red = RedBaron("[1, 2, 3]")
In [110]: red('int')
Out[110]:
0 1
1 2
2 3
In [111]: red('int').map(lambda x: x.to_python() + 1)
Out[111]:
0 2
1 3
2 4
In [112]: red('int').filter(lambda x: x.to_python() % 2 == 0)
Out[112]: 0 2
In [113]: red = RedBaron("a()\nb()\nc(x=y)")
In [114]: red('call')
Out[114]:
0 ()
1 ()
2 (x=y)
# FIXME
# red('call').map(lambda x: x.append_value("answer=42"))
In [115]: red('call')
Out[115]:
0 ()
1 ()
2 (x=y)
In [116]: red = RedBaron("a()\nb()\nc(x=y)")
# FIXME
# red('call').apply(lambda x: x.append_value("answer=42"))
.replace()¶
.replace()
is a method that allow to replace in place a node by
another one. Like every operation of this nature, you can pass a string, a
dict, a list of length one or a node instance.
In [117]: red = RedBaron("a()\nb()\nc(x=y)")
In [118]: red[2].replace("1 + 2")
In [119]: red
Out[119]:
0 a()
1 b()
2 1 + 2
In [120]: red[-1].replace("plop")
In [121]: red
Out[121]:
0 a()
1 b()
2 plop
.edit()¶
Helper method that allow to edit the code of the current node into an editor. The result is parsed and replace the code of the current node.
In [122]: red = RedBaron("def a(): return 42")
# should be used like this: (I can't execute this code here, obviously)
# red.return_.edit()
By default, the editor is taken from the variable EDITOR
in the
environment variables. If this variable is not present, nano is used. You can
use a different editor this way: node.edit(editor="vim")
.
.absolute_bounding_box¶
The absolute bounding box of a node represents its top-left and
bottom-right position relative to the fst’s root node. The position is
given as a tuple (line, column)
with both starting at 1.
In [123]: red = RedBaron("def a(): return 42")
In [124]: red.funcdef.value.absolute_bounding_box
Out[124]: BoundingBox (Position (1, 10), Position (2, 0))
You can also get the bounding box of “string” nodes like the left
parenthesis in the example above by giving the attribute’s name to the
get_absolute_bounding_box_of_attribute()
method:
In [125]: red.funcdef.get_absolute_bounding_box_of_attribute('(')
Out[125]: BoundingBox (Position (1, 6), Position (1, 6))
This is impossible to do without giving the attribute’s name as an argument since the left parenthesis is not a redbaron Node.
.bounding_box¶
Every node has the bounding_box
property which holds the
top-left and bottom-right position of the node. Compared to the
absolute_bounding_box
property, it assumes the node is the
root node so the top-left position is always (1, 1)
.
In [126]: red = RedBaron("def a(): return 42")
In [127]: red.funcdef.value.absolute_bounding_box
Out[127]: BoundingBox (Position (1, 10), Position (2, 0))
In [128]: red.funcdef.value.bounding_box
Out[128]: BoundingBox (Position (1, 1), Position (2, 0))
.find_by_position()¶
You can find which node is located at a given line and column:
In [129]: red = RedBaron("def a(): return 42")
In [130]: red.find_by_position((1, 5))
Out[130]: def a(): return 42
In [131]: red.find_by_position((1, 6)) # '(' is not a redbaron node
Out[131]: def a(): return 42
.at()¶
Returns first node at specific line
In [132]: red = RedBaron("def a():\n return 42")
In [133]: red.at(1) # Gives DefNode
Out[133]:
def a():
return 42
In [134]: red.at(2) # Gives ReturnNode
Out[134]: return 42
Node.from_fst()¶
Node.from_fst()
is a helper class method that takes an FST node and return a
RedBaron node instance. Except if you need to go down at a low level or that
RedBaron doesn’t provide the helper you need, you shouldn’t use it.
In [135]: from redbaron import Node
In [136]: Node.from_fst({"type": "name", "value": "a"})
Out[136]: a
Node.from_fst()
takes 2 optional keywords arguments: parent
and
on_attribute
that should respectively be RedBaron node instance (the
parent node) and a string (the attribute of the parent node on which this node
is stored). See .parent doc for a better understanding of those 2
parameters.
In [137]: red = RedBaron("[1,]")
In [138]: new_name = Node.from_fst({"type": "name", "value": "a"}, parent=red[0], on_attribute="value")
In [139]: red[0].value.append(new_name)
NodeList.from_fst()¶
Similarly to Node.from_fst()
, NodeList.from_fst()
is a helper
class method that takes an FST node list and return a RedBaron node list
instance. Similarly, you probably don’t need to go so low level.
In [140]: from redbaron import NodeList
In [141]: NodeList.from_fst([{"type": "name", "value": "a"}, {'first_formatting': [], 'type': 'comma', 'second_formatting': [{'type': 'space', 'value': ' '}]}, {"type": "name", "value": "b"}])
Out[141]:
0 a
1 ,
2 b
.insert_before .insert_after¶
One thing you often wants to do is to insert things just after or before the node you’ve just got via query. Those helpers are here for that:
In [142]: red = RedBaron("foo = 42\nprint('bar')\n")
In [143]: red
Out[143]:
0 foo = 42
1 print('bar')
In [144]: red.print_.insert_before("baz")
In [145]: red
Out[145]:
0 foo = 42
1 baz
2 print('bar')
In [146]: red.print_.insert_after("foobar")
In [147]: red
Out[147]:
0 foo = 42
1 baz
2 print('bar')
3 foobar
Additionally, you can give an optional argument offset
to insert more
than one line after or before:
In [148]: red = RedBaron("foo = 42\nprint('bar')\n")
In [149]: red
Out[149]:
0 foo = 42
1 print('bar')
In [150]: red.print_.insert_before("baz", offset=1)
In [151]: red
Out[151]:
0 baz
1 foo = 42
2 print('bar')
In [152]: red[0].insert_after("foobar", offset=1)
In [153]: red
Out[153]:
0 baz
1 foo = 42
2 foobar
3 print('bar')