Jzamb said he was going to start work with rbtool’s commands, so I thought I’d write a blog entry that helps new ppl get acquainted with the work flow. Please note that I will update this as I get a better understanding of the rbtool commands codebase [WIP]. Excuse spelling, format, grammar errors. Feel free to email/comment for any misunderstandings or corrections.
Highlevel Concept – The idea behind “RB Commands” is to make a uniform way of interacting with Review Board’s API. The user would type something like “rb <cmd> <args>.” As you can see, the prefix “rb” is used for all commands. This is similar to “post-review” and in fact, most the things that rb commands will do are already performed by the post-review command. However, this new approach to commands is cleaner in the sense that 1 script = 1 command, versus 1 script = many commands.
Currently, much of the rb commands are in an “experimental” phase where the code is held on Steven Macleod’s repository. Not sure when it’ll be migrated to the main codebase/master, but for now it’s here.
If you want to pull the code down locally, do the following:
1. Change working directory to the rbtools folder
2. Add my repository as a remote:
3. Create a tracking branch for the rb-commands branch:
git checkout -t smacleod/rb-commands
Depending on your version of Git, the last command may not work. Please let me know, as I’ve lost the exact command I used to pull down the branch.
Adding a command involves some simple steps, so that the rbtools package recognizes your command.
In /src/rbtools/setup.py, you’ll need to enter your new command in the array called “rb_commands.” This is where you define the location and class of your command, along with its actual name.
For ex. if patch was the name, type something like ‘patch = rbtools.commands.rbpatch:Patch‘.
After you add a new command, you’ll need to update the environment by running ‘python setup.py develop’
Inside /src/rbtools/rbtools/commands, most of your work will be done here. __init__.py has the base command class that all commands will inherit from. This class is useful for taking care of options, arguments, error handling, and etc.
I would recommend taking a look at Steven’s rbpost.py script to grasp a good understanding on how commands are setup. I would even copy it and setup my new command based of that.
Let’s examine rbpost.py’s main points of interest. Each command that inherits Command will have an “entry point” or a place of starting. This is the method main(self, *args).
The main method takes in arguments from the command line. Tip: You can actually override the arguments main(self, my_arg_1, my_arg_2, *args) to force/require arguments. So say the cmd is called “rb patch” but you want to require a request id, the user will then need to type “rb patch 3242” if main looked like this main(self, request_id, *args)
Options (not to be confused with args, which are required, not optional) are defined in the options_list. The tricky part is to know which options are needed for your command. Defining or modifying options should be straight-forward as seen by rbpost.py’s examples. In the __init__ method, you’ll also have the ability to obtain runtime defaults for options. Be sure to correlate these options with the options you’ve defined in options_list.
Interacting with WEB API
For certain commands, you’ll most likely want to connect with the RB Server, perhaps download a diff/patch file or interact in some other way. You’ll be using the WEB API, though a layer of abstraction, which is good because you won’t need to be sending raw http RESTful requests. Setting up the RBClient can be found in rbpost.py. This is the object that will allow you to access the api to do all kinds of cool things. Check out http://www.reviewboard.org/docs/manual/dev/webapi/2.0/ to get an idea of the things you can do with the API. Items and resources are organized in a tree structure. So, let’s say you have an instance of RBClient, called it rb_api. Then you’ll access the ‘root’ of the tree with rb_api.get_root(). With the root, you’re open to the whole tree of resources/commands. Let’s say you want to get a patch file for a specific request and revision of that patch. You’d type something like self.root.get_review_requests().get_item(<request_id>).get_diffs().get_item(<patch_id>).diff . This type of structure can be derived from the aforementioned link which displays the tree structure you can traverse to get to your goal.