GitHub API v2 on Ruby, take 2

Posted by admin, Sun Apr 19 12:50:00 UTC 2009


Major updates to octopi library (that is a Ruby interface for the GitHub API v2).

Gem installation

Yes! Now you can install octopi as a Gem:

$ sudo gem install fcoury-octopi --source http://gems.github.com

Progress

I am now focusing on having complete coverage of the API features that anyone can do as an anonymous user (not authenticated to GitHub, that is).

Once this part is done we’ll start working on features that requires authentication.

Some code snippets for your delight:

User API

Getting user information

# user information
u = User.find("fcoury")
puts "#{u.name} followed by #{u.followers.join(", ")}, 
following #{u.following.join(", ")}"

Followers and following, rendered as an User object collection

The bang version of followers and following creates one User object for each user login found and obviously is a lot more expensive and should be used with parsimony.

user.followers!.each do |u|
  puts "  - #{u.name} (#{u.login}) has 
#{u.public_repo_count} repo(s)"
end

Searching users

users = User.find_all("silva")
puts "#{users.size} users found for 'silva':"
users.each do |u|
  puts "  - #{u.name}"
end

Repositories API

Search

repos = Repository.find_all("ruby", "git")
puts "#{repos.size} repository(ies) with 'ruby' and 'git':"
repos.each do |r|
  puts "  - #{r.name}"
end

Getting a repository

repo = Repository.find("fcoury", "octopi")

Getting a repository for a given user

If you have an User object, you can easily do:

repo = user.repository("octopi")

Repository information

puts "#{repo.name} - #{repo.description} 
(by #{repo.owner}) - #{repo.url}"

Tags

tags = repo.tags.map {|t| t.name}.join(", ")
puts "Tags: #{tags}"

Commits API

Commits of a given repository:

fc = repo.commits.first
puts "First commit: " 
puts "#{fc.id} - #{fc.message} - by #{fc.author['name']}"

Single commit information:

puts "Diff:"
fc.details.modified.each do |m| 
  puts "#{m['filename']} DIFF: #{m['diff']}"
end

Stay tuned for upcoming updates.

1 comment | Filed Under: Ruby | Tags:

Ruby interface to GitHub API

Posted by admin, Sat Apr 18 01:11:00 UTC 2009

Update: I have made major updates to this API, take a look at this new article.

I spent the last couple of hours baking a Ruby wrapper for the GitHub API v2.

Right now their API interface appears to be down, so I wanted to put this out there and open up for forking.

Here is an example of how the API works:

DSL flavor (work in progress)

include Octopi
connect "fcoury", "<<user-token>>" do |git|
  # the contents of the key whose title is "Local Server"
  puts git.keys.find { |k| k.title == "Local Server" }.key

  # prints current user name
  puts git.user.name

  # sets user name to Fernanda
  # and saves it on GitHub
  git.user.name = "Fernanda"
  git.user.save
end

API flavor

# initializes the API and authenticates the user
github = Octopi::Api.new('fcoury', '<<user-token>>')

# the contents of the key whose title is "Local Server"
puts github.keys.find { |k| k.title == "Local Server" }.key

# retrieves current user information and prints the name
user = github.user
puts user.name

# sets user name to Fernanda
# and saves it on GitHub
user.name = "Fernanda"
user.save

For it away here:
http://github.com/fcoury/octopi

0 comments | Filed Under: Ruby | Tags:

Mimicking MySQL's AES_ENCRYPT and AES_DECRYPT in pure Ruby

Posted by admin, Tue Mar 24 11:27:00 UTC 2009

I had a problem where I needed to interact with a legacy database that relies on MySQL’s AES_ENCRYPT and AES_DECRYPT functions for data encryption for a couple of fields.

After searching “the intertnets” for it, no ready-made solution was found. So, I started with MySQL documentation on the functions:

AES_ENCRYPT() and AES_DECRYPT() allow encryption and decryption of data using the official AES (Advanced Encryption Standard) gorithm, previously known as “Rijndael.” Encoding with a 128-bit key length is used, but you can extend it up to 256 bits by modifying the source. We chose 128 bits because it is much faster and it is secure enough for most purposes.

AES_ENCRYPT() encrypts a string and returns a binary string. AES_DECRYPT() decrypts the encrypted string and returns the original string. The input arguments may be any length. If either argument is NULL, the result of this function is also NULL.

Because AES is a block-level algorithm, padding is used to encode uneven length strings and so the result string length may be calculated using this formula:

16 × (trunc(string_length / 16) + 1)

If AESDECRYPT() detects invalid data or incorrect padding, it returns NULL. However, it is possible for AESDECRYPT() to return a non-NULL value (possibly garbage) if the input data or the key is invalid.

However, after doing a lot of experimentation (as pointed out on this two posts), I downloaded MySQL code and, even though C is not my thing, I figured something out (with the help of this other blog post):

“The algorithm just creates a 16 byte buffer set to all zero, then loops through all the characters of the string you provide and does an assignment with bitwise OR between the two values. If we iterate until we hit the end of the 16 byte buffer, we just start over from the beginning doing ^=. For strings shorter than 16 characters, we stop at the end of the string.”

And here’s the relevant snippet of MySQL’s source file my_aes.c:

bzero((char*) rkey,AES_KEY_LENGTH/8);      /* Set initial key  */

for (ptr= rkey, sptr= key; sptr < key_end; ptr++,sptr++)
{
  if (ptr == rkey_end)
    ptr= rkey;  /*  Just loop over tmp_key until we used all key */
  *ptr^= (uint8) *sptr;
}

So, I finally came up with the method below, that replicates the same behavior, in Ruby (kudos to Rob Biedenharn, from Ruby Forum, who helped me to refactor this):

def mysql_key(key)
   final_key = "\0" * 16
   key.length.times do |i|
     final_key[i%16] ^= key[i]
   end
   final_key
end

Once you have the correct key, everything becomes a lot simpler. I have used OpenSSL::Cipher::AES128.new("ECB") from openssl built-in library and things worked just fine.

If you ever need to replicate the behavior of those functions on your own Ruby code, here’s a permanent link for a Gist with complete source code:

http://gist.github.com/84093

Hope it helps ;)

934 comments | Filed Under: Ruby | Tags:

Quickie: Rails migrations shortcut

Posted by fcoury, Sat Feb 07 18:57:00 UTC 2009

Silly small trick to make editing new migrations easier on a Mac environment.

Just add this little function to your .profile file:

function mig {
  mate `script/generate migration $1|tail -n1|sed 's/.*create  //'`
}

And then just run:

$ mig AddSomethingToSomewhere

From within your Rails application root folder.

This will execute the migration and open TextMate with the generated rb file right away. Cool.

For this and more tips for your Mac shell, take a look at my profile on my dotfiles repo on GitHub: http://github.com/fcoury/dotfiles

Please share your own tricks on the comments below.

0 comments | Filed Under: Mac OS X Ruby | Tags:

Blog premier

Posted by fcoury, Fri Feb 06 19:55:00 UTC 2009

Well, I just stumbled upon something that I think is not really a bug, but rather a lack of consistency between some ActionView tag helper API methods.

For instance, let’s say you want to create a form with a radio button:

<%= radio_button_tag "webserver", "apache2.2" %>

And then, in order to create the label for it, you read the documentation:

Creates a radio button; use groups of radio buttons named the same to allow users to select from a group of options.

Hmmm… Nice! Those rails folks are really awesome, they even bothered throwing in some examples:

radio_button_tag 'gender', 'male'
=> <input id="gender_male" name="gender" 
          type="radio" value="male" />

“Got it!”, you think. “It’s just add the name and the value, separated by underscore and we’re good to go!”. Clickety, clickety:

<%= label_tag "webserver\_apache2.2", 
              "Yes, I want this Native American server dude!" %>

Then, really proud of your self you go and pay localhost, port 3000 a visit. Without further delay you go blindly clicking the label.

After a brief pause, you look at your mouse, and wonder: “What the fsck?”.

You just realized clicking on the label didn’t select the radio button as you’d expect. “Okay, that was way too easy indeed, I must have typed something wrong… Yeah, for sure…”.

You go back to your code, check everything’s right, touch absolutely nothing – and you RELOAD!

<philosophical_moment> Yeah, even though you changed nothing on your code, reloading sometimes works as a demonstration of faith on the force up above. I don’t know about you, but sometimes I just reload as if that was a sign I have faith and I deserved working code for that :). </philosophical_moment>

So, after loosing faith on Saint Reload, you start forensics: left click > show me the money code. You chase after that nasty little radio button tag, and stuck inside some unaligned divs, with more classes than a MIT student, you find it:

<input id="webserver_apache22" name="webserver" 
       type="radio" value="apache2.2" />

“Nah, c’mon!”, you think, “How come that motherf little pretty radio button ate my dot?”. It turns out that the radio_button_tag handles the id generation differently from label_tag. Here’s how label_tag sanitizes the id:

def label_tag(name, text = nil, options = {})
    content_tag :label, text || name.to_s.humanize, 
        { "for" => sanitize_to_id(name) }
        .update(options.stringify_keys)
end

And here’s sanitize_to_id:

# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
    name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
end

Finally, if you check how radio_button_tag comes up with its id:

def radio_button_tag(name, value, checked = false, options = {})
    pretty_tag_value = 
        value.to_s.gsub(/\s/, "_").gsub(/(?!-)\W/, "")
            .downcase
    pretty_name = 
        name.to_s.gsub(/\[/, "_").gsub(/\]/, "")
    html_options = {
        "type" => "radio", 
        "name" => name, 
        "id" => "#{pretty_name}_#{pretty_tag_value}", 
        "value" => value }.update(options.stringify_keys)
    html_options["checked"] = "checked" if checked
    tag :input, html_options
end

You’ll realize that the rules are slightly different. As I said: for me it’s more a lack of coherence among those methods than really a bug.

Here’s a live manifestation of the bug I created for helping out:
http://labelbug.felipecoury.com

And the project on GitHub:
http://github.com/fcoury/labelbug/tree/master

So, I was good to point what’s wrong, but unfortunately I am still not that comfortable as to point a solution, so I will leave this one to the experts for now. I’ll just go back to praying that someday Saint Reload guides me through the miracle of self-healing code.

0 comments | Filed Under: Rails | Tags:

Clicky Web Analytics