Custom bash auto-completion using Ruby
Using what I showed you in the how to run Ruby code from within a bash script post, I will follow that up with an easy way to create a custom bash auto-completion function.
The Scenario…
I like to keep all my code and projects in my ~/Development directory. I typically create an alias which takes me to this path, and depending what projects I’m working on, I’ll add/remove aliases accordingly to get to the relevant projects quickly. But this is quite manual and cumbersome, and it requires constant maintenance. What I’d like is an auto-complete function that dynamically picks up any new directory I create in the ~/Development directory.
All the functions detailed below should either go into your
.bashrcfile, or a file that is sourced from your.bashrcfile.
Get rid of that alias
The first thing to do is change my alias to a function. So, instead of this…
alias d="cd ~/Development"
…we want this:
function d { cd ~/Development/$1; }
Create the list of choices
Next, we need a function which returns a list of all the directories in the ~/Development directory, which will serve as our auto-completion choices. We can use Ruby here to make things nice and clean…
#!/usr/bin/env sh
/usr/bin/env ruby <<-EORUBY
class ProjectCompletion
def initialize(command)
@command = command
end
def matches
projects.select do |task|
task[0, typed.length] == typed
end
end
def typed
@command[/\s(.+?)$/, 1] || ''
end
def projects
%x[ls ~/Development].split
end
end
puts ProjectCompletion.new(ENV["COMP_LINE"]).matches
EORUBY
Bootstrap it!
Finally, all we need is to tell bash to use this script. We do that with this command (your own paths may vary of course):
complete -C ~/.dotfiles/completion_scripts/project_completion -o default d
Reload your shell and voila! You now have bash auto-completion for all your coding projects. This is very easily customised too, so you can create auto-complete scripts for all kinds of other things, locations, tasks etc.
You can now easily reach your coding projects using auto-complete, without the need for aliases. For example, if you had a directory structure like this…
~/Development ~/Development/project_1 ~/Development/project_2
…you could easily see a list of projects doing this:
d proj<tab>
For more information see my dotfiles project.
Hope this helps.
Setting up Ubuntu 11.10 for Rails development… Just Right!
Further to my previous post on how to setup Ubuntu 11.10 for Rails development, I have now created a bash installer script to automate the whole laborious process for your pleasure… well it’s for my own pleasure really, but I like to share!
There are two installer scripts:
- Ubuntu user will setup your Ubuntu 11.10 environment will all the usual goodies you normally should install after a clean install / upgrade.
- Rails developer will setup your Ruby environment using RVM, along with all the supporting things that you will need to get up and running in no time.
The installers are updated regularly so check the git pages for full details, but some of the key things that are installed are…
The Ubuntu user installer will hook you up with these (among others)…
- Medibuntu repositories
- Ubuntu Restricted Extras
- Configuration Editor (dconf-tools)
- Compiz Config Settings Manager
- Synaptic
- Terminator
- GNOME Tweak Tool
- VLC
- Jupiter
- Caffeine
- Web apps and Sushi file previewer
- Simple LightDM Manager
- Sysmonitor App Indicator
- Dropbox
- OpenJDK (Java 7) – optional
Some of the things the Rails developer installer will setup are…
- SSH key – optional
- Essential Ubuntu libraries
- Ruby 1.8.7 from Ubuntu repostories with additional libraries
- Git and Git Gui
- MySQL 5.1 with admin GUI tools
- MongoDB 2.0.1 – optional
- ack
- ImageMagick
- RVM (Ruby Version Manager) with these rubies: 1.8.7 stable and 1.9.2 stable
Head over to the git repository for instructions on how to run the installers and also for more details about what it actually installs:
https://github.com/edjames/just_right
Simples.
Nice & clean config using dotfiles
Clean up your shell environment by using my dotfiles project.
It’s as easy as running a simple rake task… then you have a clean, customizable shell environment all set up and ready to go!
Please see the git repository for more details on what is does and how to install.
You can find it all here: https://github.com/edjames/dotfiles
As mentioned before, you will have to have Rake installed before you run the automated installer, but it will set things up nicely. Just right in fact.
Hope you like it.
Setting up Ubuntu 11.10 for Rails development
This method is depricated and has been replaced by an automated installer. See this post for more details:
Setting up Ubuntu 11.10 for Rails development… Just Right!
The setup for Ubuntu 11.10 almost identical to the 11.04 setup. A few libraries have changed but otherwise it’s exactly the same.
As before, I like to have a system Ruby installed. This has always been a precautionary measure but has saved me plenty of pain with past installs.
sudo apt-get install build-essential openssl ri ruby ruby1.8 ruby-dev rake libruby1.8 git-gui gitk libxslt1-dev libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison
Then install RVM (see the RVM website for the latest installation instructions).
$ bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
Then run the following command, and… READ THE NOTES!
rvm notes
Make sure you read the RVM post-installation notes. You will get an updated list of OS dependencies and instructions on how to modify your .bash file. If you don’t do this you will probably drive yourself crazy trying to get RVM to work. You will fail.
Finally, install whatever rubies you want:
rvm install 1.8.7 rvm install 1.9.2 rvm install 1.9.3
Finally, install MySQL (and the MySQL GUI tools)…
sudo apt-get install mysql-server libmysqlclient-dev libmysql-ruby mysql-admin
Happy coding!
Run Ruby code from within a shell script
If you need to run Ruby code from within a shell script, you could easily execute a Ruby script with a simple bash command. This means you will have two files – one bash script and one Ruby script.
However, what if you want to combine the two file into a single file?
Simple, using a heredoc in your bash script, we simply pass the entire heredoc to the ruby executable. Create a file called hybrid which contains the following code:
#!/usr/bin/env sh echo "This is bash!" /usr/bin/env ruby <<-EORUBY puts 'This is ruby!' EORUBY
Running the above bash script will produce this:
$ . hybrid This is bash! This is ruby!
Hope this helps.
Active Record Migrations without a Rails application… sort of.
If you’ve ever wanted to take advantage of Active Record’s powerful migrations functionality without the need for a Rails application, here’s how to do it.
Rather than go through all the details here, I’ve created a small github repository with a fully-functional demo. It’s easy to follow and has comments throughout the code. In fact, there’s very little code at all!
Here’s what you’ll get:
- Active Record migrations without a Rails web application
- A stripped-down list of available Rake tasks – only the ones you need. No filler.
- A clean, DSL-like syntax for generating new migrations, all through the use of Rake tasks.
Hopefully this will prove to be useful – we’re about to roll this out to our production environments (with a few mods).
Here’s the code: https://github.com/edjames/ar_migrations
Simples.
Passing parameters to a Rake task
I’ve been building a few custom rake tasks of late and I wanted to pass a parameter to one of my tasks. Typically you would use a named variable to do this. However, I wanted a cleaner approach, something which was closer to the syntax used for a Rails generator.
So instead of this…
$ rake say_hello NAME=eddie
…or, god forbid, this…
$ rake say_hello[eddie]
…I wanted something more like this…
$ rake say_hello eddie
Hmm. Seems easy enough, but there’s a catch. Let’s look at an example.
Method 1: Using a named variable
task :say_hello do
name = ENV['NAME']
puts "Hello, #{name}."
end
This is the typical approach, examples of which you will find everywhere. Running the task is easy and the intention of the command is clear:
$ rake say_hello NAME=eddie => Hello, eddie.
Nothing new here.
Method 2: Using arguments
task :say_hello, :name do |t, args|
name = args.name
puts "Hello, #{name}."
end
You would run this task like so…
$ rake say_hello[eddie] => Hello, eddie.
This is a slightly more cryptic approach, which I don’t like at all. The syntax is unintuitive and totally obscures the intention of the command, not to mention the use of square parenthesis to wrap the argument. Ugly!
Method 3: Custom black magic!
task :say_hello do
name = ARGV.last
puts "Hello, #{name}."
task name.to_sym do ; end
end
For me, this method gives us a much more DSL-like syntax, which seems cleaner and more intuitive (this is obviously entirely dependant on your own set of circumstances). To run the task using this method, you would do this…
$ rake say_hello eddie => Hello, eddie.
A few things are happening here that are worth an explanation.
Firstly, we are using the ARGV collection of command line arguments to grab the value for our argument. Standard Ruby stuff. Beware though, that this collection will contain all the arguments for Rake, not our task. That’s why we grab the last element in the collection because the first element is the name of our rake task. Modifying the task as follows will illustrate this point:
task :say_hello do puts ARGV.inspect name = ARGV.last task name.to_sym do ; end end
$ rake say_hello eddie => ["say_hello", "eddie"]
As you can see, the ARGV collection contains two elements: the name of our rake task and the value of the argument we are trying to set. Grab the last one and we’re halfway there.
Secondly, we have to define a new rake task on the fly with the same name as the value of our argument. If we don’t do this, rake will attempt to invoke a task for each command line argument.
$ rake say_hello eddie
By default, the above command will first invoke a task called “say_hello”, and then try to invoke a task called “eddie”. This behaviour is hard-coded into rake. So, we simply define a “no op” task with the corresponding name (“no op” means “no operation”). This avoids an exception being thrown when rake inevitably doesn’t find a defined task of that name.
There you have it. A DSL-like syntax for passing arguments to a rake task.
Simples.