Creating and Applying Changes¶
Once you’ve found some interesting Nodes, you can generate changes to the underlying code and apply them to the project.
Here’s how that works:
- Create a ChangeSet object (
code_monkey.edit.ChangeSet
). This tracks a collection of changes and prevents conflicts. - Create Change objects, which specify a region of code to overwrite or append to, and what new code should go there.
- Add Changes to your ChangeSet (if two changes overlap, this will raise an exception).
- Preview your changes using
ChangeSet.diff()
. - Apply your changes to the filesystem using
ChangeSet.commit()
.
Creating Change objects by hand would be extremely tedious, so we’ve got a
bunch of helpers on every Node to generate them. They live under the Node’s
.change
property.
Here’s what it looks like in action – we’ll make some edits to the code_monkey test project:
from code_monkey.node_query import project_query
from code_monkey.edit import ChangeSet
q = project_query('./test_project')
# select every class in the project
targets = q.flatten().classes()
changeset = ChangeSet()
for class_node in targets:
changeset.add(class_node.change.inject_at_body_line(0,
' foo = "bar"\n'))
print(changeset.diff())
The output, then is a typical diff:
Changes:
--- ./test_project/lib/edge_cases.py
+++ ./test_project/lib/edge_cases.py
@@ -6,6 +6,7 @@
Underground,
CanHire,
ProducesPonkeys):
+ foo = "bar"
pass #Lair body starts here
#multiline function signature
@@ -25,4 +26,5 @@
#uses dots (a 'getattr node') when specifying parent class
class WeirdSubclass(datetime.datetime):
+ foo = "bar"
pass #WeirdSubclass body starts here--- ./test_project/lib/employee.py
+++ ./test_project/lib/employee.py
@@ -2,6 +2,7 @@
from test_project import settings
class Employee(object):
+ foo = "bar"
def __init__(self, first_name, last_name):
self.first_name = first_name
@@ -13,6 +14,7 @@
class CodeMonkey(Employee):
+ foo = "bar"
"""He writes code."""
def __init__(self, *args, **kwargs):
super(CodeMonkey, self).__init__(*args, **kwargs)
If you’re happy with your changes, you can apply them by changing the last
line from print(changeset.diff())
to changeset.commit()
.
The ChangeGenerator API¶
Most of the actual power here is in the .change
property, which is an
instance of the code_monkey.change.ChangeGenerator
class. ChangeGenerators
contain a variety of methods that return Change
objects representing common
transformations. Here’s everything that ChangeGenerator can do:
-
class
code_monkey.change.
ChangeGenerator
(node)[source]¶ Generates change tuples for a specific node. Every node with a source file should have one, as its .change property.
So, a typical use might be: change = node.change.overwrite(“newtext”)
-
inject_after
(inject_source)[source]¶ Generate a change that inserts inject_source starting on the line after this node.
-
inject_at_body_index
(index, inject_source)[source]¶ Generate a change that inserts inject_source into the node, starting at index. index is relative to the beginning of the node body, not the beginning of the file.
-
inject_at_body_line
(line_index, inject_source)[source]¶ As inject_at_body_index, but takes a line index instead of a character index.
-
inject_at_index
(index, inject_source)[source]¶ Generate a change that inserts inject_source into the node, starting at index. index is relative to the beginning of the node, not the beginning of the file.
-
inject_at_line
(line_index, inject_source)[source]¶ As inject_at_index, but takes a line index instead of a character index.
-
inject_before
(inject_source)[source]¶ Generate a change that inserts inject_source starting on the line before this node.
-
Change Objects¶
If there’s something particularly finnicky that you can’t do with .change
,
you can always create Change objects by hand (that’s all that
ChangeGenerator does). The API is:
-
class
code_monkey.change.
Change
(path, start, end, new_text)[source]¶ A single change to make to a single file. Replaces the file content from indices start through (end-1) with new_text.
Parameters:
Note that you can insert without overwriting by making the start
and
end
parameters the same.