Permalink
Browse files

vendor octopi changes for now

  • Loading branch information...
1 parent 7f8f9bb commit 7eadd8add377de8a8a047d221eab4d1b248b4ef7 @holman committed May 1, 2010
Showing with 3,748 additions and 2 deletions.
  1. +2 −0 bin/tissues
  2. +0 −1 lib/tissues.rb
  3. +1 −1 tissues.gemspec
  4. +4 −0 vendor/octopi-0.2.9/.gitignore
  5. BIN vendor/octopi-0.2.9/.yardoc
  6. +9 −0 vendor/octopi-0.2.9/CHANGELOG.md
  7. +20 −0 vendor/octopi-0.2.9/LICENSE
  8. +144 −0 vendor/octopi-0.2.9/README.markdown
  9. +94 −0 vendor/octopi-0.2.9/Rakefile
  10. +4 −0 vendor/octopi-0.2.9/VERSION.yml
  11. +100 −0 vendor/octopi-0.2.9/contrib/backup.rb
  12. +20 −0 vendor/octopi-0.2.9/examples/authenticated.rb
  13. +18 −0 vendor/octopi-0.2.9/examples/issues.rb
  14. +50 −0 vendor/octopi-0.2.9/examples/overall.rb
  15. +5 −0 vendor/octopi-0.2.9/lib/ext/string_ext.rb
  16. +136 −0 vendor/octopi-0.2.9/lib/octopi.rb
  17. +213 −0 vendor/octopi-0.2.9/lib/octopi/api.rb
  18. +115 −0 vendor/octopi-0.2.9/lib/octopi/base.rb
  19. +25 −0 vendor/octopi-0.2.9/lib/octopi/blob.rb
  20. +31 −0 vendor/octopi-0.2.9/lib/octopi/branch.rb
  21. +11 −0 vendor/octopi-0.2.9/lib/octopi/branch_set.rb
  22. +20 −0 vendor/octopi-0.2.9/lib/octopi/comment.rb
  23. +69 −0 vendor/octopi-0.2.9/lib/octopi/commit.rb
  24. +35 −0 vendor/octopi-0.2.9/lib/octopi/error.rb
  25. +16 −0 vendor/octopi-0.2.9/lib/octopi/file_object.rb
  26. +28 −0 vendor/octopi-0.2.9/lib/octopi/gist.rb
  27. +111 −0 vendor/octopi-0.2.9/lib/octopi/issue.rb
  28. +7 −0 vendor/octopi-0.2.9/lib/octopi/issue_comment.rb
  29. +23 −0 vendor/octopi-0.2.9/lib/octopi/issue_set.rb
  30. +25 −0 vendor/octopi-0.2.9/lib/octopi/key.rb
  31. +14 −0 vendor/octopi-0.2.9/lib/octopi/key_set.rb
  32. +5 −0 vendor/octopi-0.2.9/lib/octopi/plan.rb
  33. +132 −0 vendor/octopi-0.2.9/lib/octopi/repository.rb
  34. +9 −0 vendor/octopi-0.2.9/lib/octopi/repository_set.rb
  35. +70 −0 vendor/octopi-0.2.9/lib/octopi/resource.rb
  36. +33 −0 vendor/octopi-0.2.9/lib/octopi/self.rb
  37. +23 −0 vendor/octopi-0.2.9/lib/octopi/tag.rb
  38. +131 −0 vendor/octopi-0.2.9/lib/octopi/user.rb
  39. +106 −0 vendor/octopi-0.2.9/octopi.gemspec
  40. +58 −0 vendor/octopi-0.2.9/test/api_test.rb
  41. +39 −0 vendor/octopi-0.2.9/test/authenticated_test.rb
  42. +20 −0 vendor/octopi-0.2.9/test/base_test.rb
  43. +23 −0 vendor/octopi-0.2.9/test/blob_test.rb
  44. +20 −0 vendor/octopi-0.2.9/test/branch_test.rb
  45. +82 −0 vendor/octopi-0.2.9/test/commit_test.rb
  46. +39 −0 vendor/octopi-0.2.9/test/file_object_test.rb
  47. +16 −0 vendor/octopi-0.2.9/test/gist_test.rb
  48. +19 −0 vendor/octopi-0.2.9/test/issue_comment.rb
  49. +33 −0 vendor/octopi-0.2.9/test/issue_set_test.rb
  50. +120 −0 vendor/octopi-0.2.9/test/issue_test.rb
  51. +29 −0 vendor/octopi-0.2.9/test/key_set_test.rb
  52. +35 −0 vendor/octopi-0.2.9/test/key_test.rb
  53. +23 −0 vendor/octopi-0.2.9/test/repository_set_test.rb
  54. +157 −0 vendor/octopi-0.2.9/test/repository_test.rb
  55. +818 −0 vendor/octopi-0.2.9/test/stubs/commits/fcoury/octopi/octopi.rb
  56. +20 −0 vendor/octopi-0.2.9/test/tag_test.rb
  57. +246 −0 vendor/octopi-0.2.9/test/test_helper.rb
  58. +92 −0 vendor/octopi-0.2.9/test/user_test.rb
View
2 bin/tissues
@@ -1,7 +1,9 @@
#!/usr/bin/env ruby
+require 'vendor/octopi-0.2.9/lib/octopi'
require 'lib/tissues'
case ARGV[0]
+when 'nonexistent'
else
Tissues::Sync.go!
end
View
1 lib/tissues.rb
@@ -2,7 +2,6 @@
require 'rubygems'
require 'things'
-require 'octopi'
require 'tissues/helpers'
require 'tissues/patches'
View
2 tissues.gemspec
@@ -43,7 +43,7 @@ Gem::Specification.new do |s|
## List your runtime dependencies here. Runtime dependencies are those
## that are needed for an end user to actually USE your code.
s.add_dependency('things-client', [">= 0.2.4"])
- s.add_dependency('octopi', [">= 0.2.8"])
+ # s.add_dependency('octopi', [">= 0.2.9"])
## List your development dependencies here. Development dependencies are
## those that are only needed during development
View
4 vendor/octopi-0.2.9/.gitignore
@@ -0,0 +1,4 @@
+examples/github.yml
+doc/
+pkg/
+contrib/nothingspecial.rb
View
BIN vendor/octopi-0.2.9/.yardoc
Binary file not shown.
View
9 vendor/octopi-0.2.9/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Changelog
+
+# 0.2.3
+
+* Ticket #39: Added sorting to _lib/octopi.rb_ in order to ensure _lib/octopi/api.rb_ and _lib/octopi/base.rb_ are loaded first [branch14]
+* Ticket #38 & #40: Added mechanize as a requirement again.
+
+
+
View
20 vendor/octopi-0.2.9/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Felipe Coury
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
144 vendor/octopi-0.2.9/README.markdown
@@ -0,0 +1,144 @@
+# octopi
+
+Octopi is a Ruby interface to GitHub API v2 (http://develop.github.com).
+
+To install it as a Gem, just run:
+
+ $ sudo gem install octopi
+
+Get notifications via Twitter, following @octopi_gem:
+http://twitter.com/octopi_gem
+
+## Authenticated Usage
+
+### Seamless authentication using .gitconfig defaults
+
+If you have your <tt>~/.gitconfig</tt> file in place, and you have a [github] section (if you don't, take a look at this [GitHub Guides entry][http://github.com/guides/tell-git-your-user-name-and-email-address], you can use seamless authentication using this method:
+
+ authenticated do
+ repo = Repository.find(:name => "api-labrat", :user => "fcoury")
+ end
+
+### Explicit authentication
+
+Sometimes, you may not want to get authentication data from _~/.gitconfig_. You want to use GitHub API authenticated as a third party. For this use case, you have a couple of options too.
+
+**1. Providing login and token inline:**
+
+ authenticated_with :login => "mylogin", :token => "mytoken" do
+ repo = Repository.find(:name => "api-labrat", :user => "fcoury")
+ issue = repo.open_issue :title => "Sample issue",
+ :body => "This issue was opened using GitHub API and Octopi"
+ puts issue.number
+ end
+
+**2. Providing login and password inline:**
+
+ authenticated_with :login => "mylogin", :password => "password" do
+ repo = Repository.find(:name => "api-labrat", :user => "fcoury")
+ issue = repo.open_issue :title => "Sample issue",
+ :body => "This issue was opened using GitHub API and Octopi"
+ puts issue.number
+ end
+
+**3. Providing a YAML file with authentication information:**
+
+Use the following format:
+
+ #
+ # Octopi GitHub API configuration file
+ #
+
+ # GitHub user login and token
+ login: github-username
+ token: github-token
+
+ # Trace level
+ # Possible values:
+ # false - no tracing, same as if the param is ommited
+ # true - will output each POST or GET operation to the stdout
+ # curl - same as true, but in addition will output the curl equivalent of each command (for debugging)
+ trace: curl
+
+ And change the way you connect to:
+
+ authenticated_with :config => "github.yml" do
+ (...)
+ end
+
+## Anonymous Usage
+
+This reflects the usage of the API to retrieve information on a read-only fashion, where the user doesn't have to be authenticated.
+
+### Users API
+
+Getting user information
+
+ user = User.find("fcoury")
+ puts "#{user.name} is being followed by #{user.followers.join(", ")} and following #{user.following.join(", ")}"
+
+The bang methods `followers!` and `following!` retrieves a full User object for each user login returned, so it has to be used carefully.
+
+ user.followers!.each do |u|
+ puts " - #{u.name} (#{u.login}) has #{u.public_repo_count} repo(s)"
+ end
+
+Searching for user
+
+ users = User.find_all("silva")
+ puts "#{users.size} users found for 'silva':"
+ users.each do |u|
+ puts " - #{u.name}"
+ end
+
+### Repositories API
+
+ repo = user.repository("octopi") # same as: Repository.find("fcoury", "octopi")
+ puts "Repository: #{repo.name} - #{repo.description} (by #{repo.owner}) - #{repo.url}"
+ puts " Tags: #{repo.tags and repo.tags.map {|t| t.name}.join(", ")}"
+
+Search:
+
+ repos = Repository.find_all("ruby", "git")
+ puts "#{repos.size} repository(ies) with 'ruby' and 'git':"
+ repos.each do |r|
+ puts " - #{r.name}"
+ end
+
+Issues API integrated into the Repository object:
+
+ issue = repo.issues.first
+ puts "First open issue: #{issue.number} - #{issue.title} - Created at: #{issue.created_at}"
+
+Single issue information:
+
+ issue = repo.issue(11)
+
+Commits API information from a Repository object:
+
+ first_commit = repo.commits.first
+ puts "First commit: #{first_commit.id} - #{first_commit.message} - by #{first_commit.author['name']}"
+
+Single commit information:
+
+ puts "Diff:"
+ first_commit.details.modified.each {|m| puts "#{m['filename']} DIFF: #{m['diff']}" }
+
+## Author
+
+* Felipe Coury - http://felipecoury.com
+* HasMany.info blog - http://hasmany.info
+
+## Contributors
+
+In alphabetical order:
+
+* Ryan Bigg - http://ryanbigg.net
+* Brandon Calloway - http://github.com/bcalloway
+* runpaint - http://github.com/runpaint
+
+Thanks guys!
+
+## Copyright
+
+Copyright (c) 2009 Felipe Coury. See LICENSE for details.
View
94 vendor/octopi-0.2.9/Rakefile
@@ -0,0 +1,94 @@
+require 'rubygems'
+require 'rake'
+require 'yaml'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gem|
+ gem.name = "octopi"
+ gem.summary = %Q{A Ruby interface to GitHub API v2}
+ gem.email = "felipe.coury@gmail.com"
+ gem.homepage = "http://github.com/fcoury/octopi"
+ gem.authors = ["Felipe Coury"]
+ gem.rubyforge_project = "octopi"
+ gem.add_dependency('nokogiri', '>= 1.3.1')
+ gem.add_dependency('httparty', '>= 0.4.5')
+ gem.add_dependency('mechanize', '>= 0.9.3')
+ gem.add_dependency('api_cache', '>= 0')
+ gem.files.exclude 'test/**/*'
+ gem.files.exclude 'test*'
+ gem.files.exclude 'doc/**/*'
+ gem.files.exclude 'examples/**/*'
+
+
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
+ end
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
+end
+
+begin
+ require 'rake/contrib/sshpublisher'
+ namespace :rubyforge do
+
+ desc "Release gem and RDoc documentation to RubyForge"
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
+
+ namespace :release do
+ desc "Publish RDoc to RubyForge."
+ task :docs => [:rdoc] do
+ config = YAML.load(
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
+ )
+
+ host = "#{config['username']}@rubyforge.org"
+ remote_dir = "/var/www/gforge-projects/octopi/"
+ local_dir = 'rdoc'
+
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
+ end
+ end
+ end
+rescue LoadError
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
+end
+
+require 'rake/testtask'
+Rake::TestTask.new(:test) do |test|
+ test.libs << 'lib' << 'test'
+ test.pattern = 'test/**/*_test.rb'
+ test.verbose = false
+end
+
+begin
+ require 'rcov/rcovtask'
+ Rcov::RcovTask.new do |test|
+ test.libs << 'test'
+ test.pattern = 'test/**/*_test.rb'
+ test.verbose = true
+ end
+rescue LoadError
+ task :rcov do
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
+ end
+end
+
+
+task :default => :test
+
+require 'rake/rdoctask'
+Rake::RDocTask.new do |rdoc|
+ if File.exist?('VERSION.yml')
+ config = YAML.load(File.read('VERSION.yml'))
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
+ else
+ version = ""
+ end
+
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = "octopi #{version}"
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
View
4 vendor/octopi-0.2.9/VERSION.yml
@@ -0,0 +1,4 @@
+---
+:major: 0
+:minor: 2
+:patch: 9
View
100 vendor/octopi-0.2.9/contrib/backup.rb
@@ -0,0 +1,100 @@
+require File.join(File.dirname(__FILE__), '..', 'lib', 'octopi')
+
+USAGE_MSG = <<EOF
+Usage: #{$0} <user> [<directory>]
+
+Performs a backup of the named user's GitHub.com data.
+
+This script will fetch the repositories, along with their metadata and
+associated issues, for the named user. It will also retrieve the user's
+profile, and those of his followers. This data will be stored in
+<directory>/<user>. If a directory is not supplied, ~/.github-backup will be
+used instead.
+EOF
+# TODO: Accept list of targets as argument. The main use case is somebody
+# wanting all of their repositories checked out, without the performane hit
+# and clutter of the extraneous metadata,
+include Octopi
+
+class Object
+ def to_yaml_file(file)
+ File.open("#{file}.yaml", 'w') do |f|
+ YAML.dump(self, f)
+ end
+ end
+end
+
+TARGETS = [:user, :followers, :repositories, :issues]
+
+@user, @basedir = ARGV
+raise ArgumentError, USAGE_MSG unless @user
+@basedir ||= File.expand_path("~/.github-backup")
+@basedir = File.join(@basedir,@user)
+TARGETS.map{|k| k.to_s}.each do |dir|
+ dir = File.join(@basedir,dir)
+ FileUtils.mkdir_p(dir) unless File.exists? dir
+end
+
+@user = User.find(@user)
+
+def user
+ puts "* Saving profile"
+ @user.to_yaml_file(@user.login)
+end
+
+def followers
+ @user.followers!.each do |follower|
+ puts "* #{follower.login} (#{follower.name})"
+ follower.to_yaml_file(follower.login)
+ end
+end
+
+def repositories
+ @user.repositories.each do |repo|
+ puts "* #{repo.name} (#{repo.description})\n---"
+
+ git_dir = File.join(repo.name,'.git')
+ # FIXME: Instead of just checking for a Git directory, we could try `git
+ # pull`, and if that indicates that the repository doesn't exist, `git
+ # clone`
+ if File.exists? git_dir
+ Dir.chdir repo.name do
+ # FIXME: If this fails, try deleting the clone and re-cloning?
+ # FIXME: Confirm this is the best solution as opposed to re-cloning
+ # every time, using `git fetch` or `git clone --mirror`.
+ system("git pull")
+ end
+ else
+ system("git clone #{repo.clone_url}")
+ end
+ repo.to_yaml_file(repo.name)
+ puts
+ end
+end
+
+# TODO: For forked repositories whose parents have issue trackers, get their
+# issues instead.
+def issues
+ FileUtils.mkdir_p @user.repositories.map{|r| r.name}
+ @user.repositories.each do |repo|
+ puts "#{repo.name}"
+ Dir.chdir(repo.name) do
+ repo.all_issues.each do |issue|
+ puts "* #{issue.title} [#{issue.state}]"
+ issue.to_yaml_file(issue.number)
+ end
+ end
+ end
+end
+
+TARGETS.each do |target|
+ target.to_s.each do |title|
+ puts title.capitalize
+ title.length.times {print '#'}
+ end
+ puts
+ Dir.chdir(File.join(@basedir, target.to_s)) do
+ send(target)
+ end
+ puts
+end
View
20 vendor/octopi-0.2.9/examples/authenticated.rb
@@ -0,0 +1,20 @@
+require File.join(File.dirname(__FILE__), '..', 'lib', 'octopi')
+
+include Octopi
+
+authenticated :trace => "curl" do |g|
+ repo = g.repository("api-labrat")
+
+ issue = repo.open_issue :title => "Sample issue",
+ :body => "This issue was opened using GitHub API and Octopi"
+ puts "Successfully opened issue \##{issue.number}"
+
+ # # labels = issue.add_label "Working", "Todo"
+ # # puts "Labels: #{labels.inspect}"
+
+ issue.close
+ puts "Successfully closed issue \##{issue.number}"
+
+ # labels = issue.remove_label "Todo"
+ # puts "Successfully removed label Todo. Current labels: #{labels.inspect}"
+end
View
18 vendor/octopi-0.2.9/examples/issues.rb
@@ -0,0 +1,18 @@
+require File.join(File.dirname(__FILE__), '..', 'lib', 'octopi')
+
+include Octopi
+
+user = User.find("fcoury")
+puts user.name
+
+repo = user.repository("octopi")
+puts repo.description
+
+issue = Issue.find_all(user.login, repo.name).first
+puts "First open issue: #{issue.number} - #{issue.title} - Created at: #{issue.created_at}"
+
+issue2 = repo.issues.first
+puts "First open issue: #{issue.number} - #{issue.title} - Created at: #{issue.created_at}"
+
+issue3 = repo.issue(issue2.number)
+puts "First open issue: #{issue.number} - #{issue.title} - Created at: #{issue.created_at}"
View
50 vendor/octopi-0.2.9/examples/overall.rb
@@ -0,0 +1,50 @@
+require File.join(File.dirname(__FILE__), '..', 'lib', 'octopi')
+
+include Octopi
+
+# user information
+user = User.find("fcoury")
+puts "#{user.name} is being followed by #{user.followers.join(", ")} and following #{user.following.join(", ")}"
+
+# the bang version of followers and following
+# fetches user object for each user, but is
+# a lot more expensive
+user.followers!.each do |u|
+ puts " - #{u.name} (#{u.login}) has #{u.public_repo_count} repo(s)"
+end
+
+# search user
+users = User.find_all("silva")
+puts "#{users.size} users found for 'silva':"
+users.each do |u|
+ puts " - #{u.name}"
+end
+
+# repository information
+# to get all repos for user: user.repositories
+repo = user.repository("octopi") # same as: Repository.find("fcoury", "octopi")
+puts "Repository: #{repo.name} - #{repo.description} (by #{repo.owner}) - #{repo.url}"
+puts " Tags: #{repo.tags and repo.tags.map {|t| t.name}.join(", ")}"
+
+issue = repo.issues.first
+puts "Sample open issue: #{issue.number} - #{issue.title} - Created at: #{issue.created_at}"
+
+# commits of a the repository
+commit = repo.commits.first
+puts "Commit: #{commit.id} - #{commit.message} - by #{commit.author['name']}"
+
+# single commit information
+# details is the same as: Commit.find(commit)
+puts "Diff:"
+commit.modified.each {|m| puts "#{m['filename']} DIFF: #{m['diff']}" }
+
+# repository search
+repos = Repository.find_all("ruby", "git")
+puts "#{repos.size} repository(ies) with 'ruby' and 'git':"
+repos.each do |r|
+ puts " - #{r.name}"
+end
+
+# connect "user", "<< token >>" do |github|
+# puts github.user.name
+# end
View
5 vendor/octopi-0.2.9/lib/ext/string_ext.rb
@@ -0,0 +1,5 @@
+class String
+ def camel_case
+ self.gsub(/(^|_)(.)/) { $2.upcase }
+ end
+end
View
136 vendor/octopi-0.2.9/lib/octopi.rb
@@ -0,0 +1,136 @@
+require 'rubygems'
+
+require 'httparty'
+require 'mechanize'
+require 'nokogiri'
+require 'api_cache'
+
+require 'yaml'
+require 'pp'
+
+# Core extension stuff
+Dir[File.join(File.dirname(__FILE__), "ext/*.rb")].each { |f| require f }
+
+# Octopi stuff
+# By sorting them we ensure that api and base are loaded first on all sane operating systems
+Dir[File.join(File.dirname(__FILE__), "octopi/*.rb")].sort.each { |f| require f }
+
+# Include this into your app so you can access the child classes easier.
+# This is the root of all things Octopi.
+module Octopi
+
+ # The authenticated methods are all very similar.
+ # TODO: Find a way to merge them into something... better.
+
+ def authenticated(options={}, &block)
+ begin
+ config = config = File.open(options[:config]) { |yf| YAML::load(yf) } if options[:config]
+ config = read_gitconfig
+ options[:login] = config["github"]["user"]
+ options[:token] = config["github"]["token"]
+
+ authenticated_with(options) do
+ yield
+ end
+ ensure
+ # Reset authenticated so if we were to do an anonymous call it would Just Work(tm)
+ Api.authenticated = false
+ Api.api = AnonymousApi.instance
+ end
+ end
+
+ def authenticated_with(options, &block)
+ begin
+
+ Api.api.trace_level = options[:trace] if options[:trace]
+
+ if options[:token].nil? && !options[:password].nil?
+ options[:token] = grab_token(options[:login], options[:password])
+ end
+ begin
+ User.find(options[:login])
+ # If the user cannot see themselves then they are not logged in, tell them so
+ rescue Octopi::NotFound
+ raise Octopi::InvalidLogin
+ end
+
+ trace("=> Trace on: #{options[:trace]}")
+
+ Api.api = AuthApi.instance
+ Api.api.login = options[:login]
+ Api.api.token = options[:token]
+
+ yield
+ ensure
+ # Reset authenticated so if we were to do an anonymous call it would Just Work(tm)
+ Api.authenticated = false
+ Api.api = AnonymousApi.instance
+ end
+ end
+
+ private
+
+ def grab_token(username, password)
+ a = Mechanize.new { |agent|
+ # Fake out the agent
+ agent.user_agent_alias = 'Mac Safari'
+ }
+
+ # Login with the provided
+ a.get('http://github.com/login') do |page|
+ user_page = page.form_with(:action => '/session') do |login|
+ login.login = username
+ login.password = password
+ end.submit
+
+
+ if Api.api.trace_level
+ File.open("got.html", "w+") do |f|
+ f.write user_page.body
+ end
+ `open got.html`
+ end
+
+ body = Nokogiri::HTML(user_page.body)
+ error = body.xpath("//div[@class='error_box']").text
+ raise error if error != ""
+
+ # Should be clear to go if there is no errors.
+ link = user_page.link_with(:text => "account")
+ @account_page = a.click(link)
+ if Api.api.trace_level
+ File.open("account.html", "w+") do |f|
+ f.write @account_page.body
+ end
+ `open account.html`
+ end
+
+ return Nokogiri::HTML(@account_page.body).xpath("//p").xpath("strong")[1].text
+ end
+ end
+
+
+ def read_gitconfig
+ config = {}
+ group = nil
+ File.foreach("#{ENV['HOME']}/.gitconfig") do |line|
+ line.strip!
+ if line[0] != ?# && line =~ /\S/
+ if line =~ /^\[(.*)\]$/
+ group = $1
+ config[group] ||= {}
+ else
+ key, value = line.split("=").map { |v| v.strip }
+ config[group][key] = value
+ end
+ end
+ end
+ config
+ end
+
+ def trace(text)
+ if Api.api.trace_level
+ puts "text"
+ end
+ end
+end
View
213 vendor/octopi-0.2.9/lib/octopi/api.rb
@@ -0,0 +1,213 @@
+require 'singleton'
+require File.join(File.dirname(__FILE__), "self")
+module Octopi
+ # Dummy class, so AnonymousApi and AuthApi have somewhere to inherit from
+ class Api
+ include Self
+ attr_accessor :format, :login, :token, :trace_level, :read_only
+ end
+
+ # Used for accessing the Github API anonymously
+ class AnonymousApi < Api
+ include HTTParty
+ include Singleton
+ base_uri "http://github.com/api/v2"
+
+ def read_only?
+ true
+ end
+
+ def auth_parameters
+ { }
+ end
+ end
+
+ class AuthApi < Api
+ include HTTParty
+ include Singleton
+ base_uri "https://github.com/api/v2"
+
+ def read_only?
+ false
+ end
+
+ def auth_parameters
+ { :login => Api.me.login, :token => Api.me.token }
+ end
+ end
+
+ # This is the real API class.
+ #
+ # API requests are limited to 60 per minute.
+ #
+ # Sets up basic methods for accessing the API.
+ class Api
+ @@api = Octopi::AnonymousApi.instance
+ @@authenticated = false
+
+ include Singleton
+ CONTENT_TYPE = {
+ 'yaml' => ['application/x-yaml', 'text/yaml', 'text/x-yaml', 'application/yaml'],
+ 'json' => 'application/json',
+ 'xml' => 'application/xml',
+ # Unexpectedly, Github returns resources such as blobs as text/html!
+ # Thus, plain == text/html.
+ 'plain' => ['text/plain', 'text/html']
+ }
+ RETRYABLE_STATUS = [403]
+ MAX_RETRIES = 10
+ # Would be nice if cattr_accessor was available, oh well.
+
+ # We use this to check if we use the auth or anonymous api
+ def self.authenticated
+ @@authenticated
+ end
+
+ # We set this to true when the user has auth'd.
+ def self.authenticated=(value)
+ @@authenticated = value
+ end
+
+ # The API we're using
+ def self.api
+ @@api
+ end
+
+ class << self
+ alias_method :me, :api
+ end
+
+ # set the API we're using
+ def self.api=(value)
+ @@api = value
+ end
+
+
+ def user
+ user_data = get("/user/show/#{login}")
+ raise "Unexpected response for user command" unless user_data and user_data['user']
+ User.new(user_data['user'])
+ end
+
+ def save(resource_path, data)
+ traslate resource_path, data
+ #still can't figure out on what format values are expected
+ post("#{resource_path}", { :query => data })
+ end
+
+
+ def find(path, result_key, resource_id, klass=nil, cache=true)
+ result = get(path, { :id => resource_id, :cache => cache }, klass)
+ result
+ end
+
+
+ def find_all(path, result_key, query, klass=nil, cache=true)
+ { :query => query, :id => query, :cache => cache }
+ result = get(path, { :query => query, :id => query, :cache => cache }, klass)
+ result[result_key]
+ end
+
+ def get_raw(path, params, klass=nil)
+ get(path, params, klass, 'plain')
+ end
+
+ def get(path, params = {}, klass=nil, format = :yaml)
+ @@retries = 0
+ begin
+ submit(path, params, klass, format) do |path, params, format, query|
+ self.class.get "/#{format}#{path}", { :format => format, :query => query }
+ end
+ rescue RetryableAPIError => e
+ if @@retries < MAX_RETRIES
+ $stderr.puts e.message
+ if e.code != 403
+ @@retries += 1
+ sleep 6
+ retry
+ else
+ raise APIError, "Github returned status #{e.code}, you may not have access to this resource."
+ end
+ else
+ raise APIError, "GitHub returned status #{e.code}, despite" +
+ " repeating the request #{MAX_RETRIES} times. Giving up."
+ end
+ end
+ end
+
+ def post(path, params = {}, klass=nil, format = :yaml)
+ @@retries = 0
+ begin
+ trace "POST", "/#{format}#{path}", params
+ submit(path, params, klass, format) do |path, params, format, query|
+ resp = self.class.post "/#{format}#{path}", { :body => params, :format => format, :query => query }
+ resp
+ end
+ rescue RetryableAPIError => e
+ if @@retries < MAX_RETRIES
+ $stderr.puts e.message
+ @@retries += 1
+ sleep 6
+ retry
+ else
+ raise APIError, "GitHub returned status #{e.code}, despite" +
+ " repeating the request #{MAX_RETRIES} times. Giving up."
+ end
+ end
+ end
+
+ private
+
+ def method_missing(method, *args)
+ api.send(method, *args)
+ end
+
+ def submit(path, params = {}, klass=nil, format = :yaml, &block)
+ # Ergh. Ugly way to do this. Find a better one!
+ cache = params.delete(:cache)
+ cache = true if cache.nil?
+ params.each_pair do |k,v|
+ if path =~ /:#{k.to_s}/
+ params.delete(k)
+ path = path.gsub(":#{k.to_s}", v)
+ end
+ end
+ begin
+ key = "#{Api.api.class.to_s}:#{path}"
+ resp = if cache
+ APICache.get(key, :cache => 61) do
+ yield(path, params, format, auth_parameters)
+ end
+ else
+ yield(path, params, format, auth_parameters)
+ end
+ rescue Net::HTTPBadResponse
+ raise RetryableAPIError
+ end
+
+ raise RetryableAPIError, resp.code.to_i if RETRYABLE_STATUS.include? resp.code.to_i
+ # puts resp.code.inspect
+ raise NotFound, klass || self.class if resp.code.to_i == 404
+ raise APIError,
+ "GitHub returned status #{resp.code}" unless resp.code.to_i == 200
+ # FIXME: This fails for showing raw Git data because that call returns
+ # text/html as the content type. This issue has been reported.
+
+ # It happens, in tests.
+ return resp if resp.headers.empty?
+ ctype = resp.headers['content-type'].first.split(";").first
+ raise FormatError, [ctype, format] unless CONTENT_TYPE[format.to_s].include?(ctype)
+ if format == 'yaml' && resp['error']
+ raise APIError, resp['error']
+ end
+ resp
+ end
+
+ def trace(oper, url, params)
+ return unless trace_level
+ par_str = " params: " + params.map { |p| "#{p[0]}=#{p[1]}" }.join(", ") if params && !params.empty?
+ puts "#{oper}: #{url}#{par_str}"
+ end
+
+ end
+end
View
115 vendor/octopi-0.2.9/lib/octopi/base.rb
@@ -0,0 +1,115 @@
+module Octopi
+ class Base
+ VALID = {
+ :repo => {
+ # FIXME: API currently chokes on repository names containing periods,
+ # but presumably this will be fixed.
+ :pat => /^[A-Za-z0-9_\.-]+$/,
+ :msg => "%s is an invalid repository name"},
+ :user => {
+ :pat => /^[A-Za-z0-9_\.-]+$/,
+ :msg => "%s is an invalid username"},
+ :sha => {
+ :pat => /^[a-f0-9]{40}$/,
+ :msg => "%s is an invalid SHA hash"},
+ :state => {
+ # FIXME: Any way to access Issue::STATES from here?
+ :pat => /^(open|closed)$/,
+ :msg => "%s is an invalid state; should be 'open' or 'closed'."
+ }
+ }
+
+ attr_accessor :api
+
+ def initialize(attributes={})
+ # Useful for finding out what attr_accessor needs for classes
+ # puts caller.first.inspect
+ # puts "#{self.class.inspect} #{attributes.keys.map { |s| s.to_sym }.inspect}"
+ attributes.each do |key, value|
+ method = "#{key}="
+ self.send(method, value) if respond_to? method
+ end
+ end
+
+ def error=(error)
+ if /\w+ not found/.match(error)
+ raise NotFound, self.class
+ end
+ end
+
+ def property(p, v)
+ path = "#{self.class.path_for(:resource)}/#{p}"
+ Api.api.find(path, self.class.resource_name(:singular), v)
+ end
+
+ def save
+ hash = {}
+ @keys.each { |k| hash[k] = send(k) }
+ Api.api.save(self.path_for(:resource), hash)
+ end
+
+ private
+
+ def self.gather_name(options)
+ options[:repository] || options[:repo] || options[:name]
+ end
+
+ def self.gather_details(options)
+ repo = self.gather_name(options)
+ repo = Repository.find(:user => options[:user], :name => repo) if !repo.is_a?(Repository)
+ user = repo.owner.to_s
+ user ||= options[:user].to_s
+ branch = options[:branch] || "master"
+ self.validate_args(user => :user, repo.name => :repo)
+ [user, repo, branch, options[:sha]].compact
+ end
+
+ def self.extract_user_repository(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ if options.empty?
+ if args.length > 1
+ repo, user = *args
+ else
+ repo = args.pop
+ end
+ else
+ options[:repo] = options[:repository] if options[:repository]
+ repo = args.pop || options[:repo]
+ user = options[:user]
+ end
+
+ user = repo.owner if repo.is_a? Repository
+
+ if repo.is_a?(String) && !user
+ raise "Need user argument when repository is identified by name"
+ end
+ ret = extract_names(user, repo)
+ ret << options
+ ret
+ end
+
+ def self.extract_names(*args)
+ args.map do |v|
+ v = v.name if v.is_a? Repository
+ v = v.login if v.is_a? User
+ v
+ end
+ end
+
+ def self.ensure_hash(spec)
+ raise ArgumentMustBeHash, "find takes a hash of options as a solitary argument" if !spec.is_a?(Hash)
+ end
+
+ def self.validate_args(spec)
+ m = caller[0].match(/\/([a-z0-9_]+)\.rb:\d+:in `([a-z_0-9]+)'/)
+ meth = m ? "#{m[1].camel_case}.#{m[2]}" : 'method'
+ raise ArgumentError, "Invalid spec" unless
+ spec.values.all? { |s| VALID.key? s }
+ errors = spec.reject{|arg, spec| arg.nil?}.
+ reject{|arg, spec| arg.to_s.match(VALID[spec][:pat])}.
+ map {|arg, spec| "Invalid argument '%s' for %s (%s)" %
+ [arg, meth, VALID[spec][:msg] % arg]}
+ raise ArgumentError, "\n" + errors.join("\n") unless errors.empty?
+ end
+ end
+end
View
25 vendor/octopi-0.2.9/lib/octopi/blob.rb
@@ -0,0 +1,25 @@
+require File.join(File.dirname(__FILE__), "resource")
+module Octopi
+ class Blob < Base
+ attr_accessor :text, :data, :name, :sha, :size, :mode, :mime_type
+ include Resource
+ set_resource_name "blob"
+
+ resource_path "/blob/show/:id"
+
+ def self.find(options={})
+ ensure_hash(options)
+ user, repo = gather_details(options)
+ sha = options[:sha]
+ path = options[:path]
+
+ self.validate_args(sha => :sha, user => :user)
+
+ if path
+ super [user, repo, sha, path]
+ else
+ Api.api.get_raw(path_for(:resource), {:id => [user, repo, sha].join('/')})
+ end
+ end
+ end
+end
View
31 vendor/octopi-0.2.9/lib/octopi/branch.rb
@@ -0,0 +1,31 @@
+module Octopi
+ class Branch < Base
+ attr_accessor :name, :sha
+ include Resource
+ set_resource_name "branch", "branches"
+
+ resource_path "/repos/show/:id"
+
+ # Called when we ask for a resource.
+ # Arguments are passed in like [<name>, <sha>]
+ # TODO: Find out why args are doubly nested
+ def initialize(*args)
+ args = args.flatten!
+ self.name = args.first
+ self.sha = args.last
+ end
+
+ def to_s
+ name
+ end
+
+ def self.all(options={})
+ ensure_hash(options)
+ user, repo = gather_details(options)
+ self.validate_args(user => :user, repo => :repo)
+ BranchSet.new(find_plural([user, repo, 'branches'], :resource)) do |i|
+ { :name => i.first, :hash => i.last }
+ end
+ end
+ end
+end
View
11 vendor/octopi-0.2.9/lib/octopi/branch_set.rb
@@ -0,0 +1,11 @@
+require File.join(File.dirname(__FILE__), "branch")
+class Octopi::BranchSet < Array
+ include Octopi
+ attr_accessor :user, :repository
+ # Takes a name, returns a branch if it exists
+ def find(name)
+ branch = detect { |b| b.name == name }
+ raise NotFound, Branch if branch.nil?
+ branch
+ end
+end
View
20 vendor/octopi-0.2.9/lib/octopi/comment.rb
@@ -0,0 +1,20 @@
+module Octopi
+ class Comment < Base
+ attr_accessor :content, :author, :title, :updated, :link, :published, :id, :repository
+ include Resource
+ set_resource_name "tree"
+
+ resource_path "/tree/show/:id"
+
+ def self.find(options={})
+ ensure_hash(options)
+ user, repo, branch, sha = gather_details(options)
+ self.validate_args(sha => :sha, user => :user, repo => :repo)
+ super [user, repo, sha]
+ end
+
+ def commit
+ Commit.find(:user => repository.owner, :repo => repository, :sha => /commit\/(.*?)#/.match(link)[1])
+ end
+ end
+end
View
69 vendor/octopi-0.2.9/lib/octopi/commit.rb
@@ -0,0 +1,69 @@
+module Octopi
+ class Commit < Base
+ include Resource
+ find_path "/commits/list/:query"
+ resource_path "/commits/show/:id"
+
+ attr_accessor :repository, :message, :parents, :author, :url, :id, :committed_date, :authored_date, :tree, :committer, :added, :removed, :modified
+
+
+ # Finds all commits for the given options:
+ #
+ # :repo or :repository or :name - A repository object or the name of a repository
+ # :user - A user object or the login of a user
+ # :branch - A branch object or the name of a branch. Defaults to master.
+ #
+ # Sample usage:
+ #
+ # >> find_all(:user => "fcoury", :repo => "octopi")
+ # => <Latest 30 commits for master branch>
+ #
+ # => find_all(:user => "fcoury", :repo => "octopi", :branch => "lazy") # branch is set to lazy.
+ # => <Latest 30 commits for lazy branch>
+ #
+ def self.find_all(options={})
+ ensure_hash(options)
+ user, repo, branch = gather_details(options)
+ commits = if options[:path]
+ super user, repo.name, branch, options[:path]
+ else
+ super user, repo.name, branch
+ end
+ # Repository is not passed in from the data, set it manually.
+ commits.each { |c| c.repository = repo }
+ commits
+ end
+
+ # Finds all commits for the given options:
+ #
+ # :repo or :repository or :name - A repository object or the name of a repository
+ # :user - A user object or the login of a user
+ # :branch - A branch object or the name of a branch. Defaults to master.
+ # :sha - The commit ID
+ #
+ # Sample usage:
+ #
+ # >> find(:user => "fcoury", :repo => "octopi", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae")
+ # => <Commit f6609209c3ac0badd004512d318bfaa508ea10ae for branch master>
+ #
+ # >> find(:user => "fcoury", :repo => "octopi", :branch => "lazy", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae") # branch is set to lazy.
+ # => <Commit f6609209c3ac0badd004512d318bfaa508ea10ae for branch lazy>
+ #
+ def self.find(options={})
+ ensure_hash(options)
+ user, repo, branch, sha = gather_details(options)
+ super [user, repo, sha]
+ end
+
+ def repo_identifier
+ url_parts = url.split('/')
+ if @repository
+ parts = [@repository.owner, @repository.name, url_parts[6]]
+ else
+ parts = [url_parts[3], url_parts[4], url_parts[6]]
+ end
+
+ parts.join('/')
+ end
+ end
+end
View
35 vendor/octopi-0.2.9/lib/octopi/error.rb
@@ -0,0 +1,35 @@
+module Octopi
+
+ class FormatError < StandardError
+ def initialize(f)
+ super("Got unexpected format (got #{f.first} for #{f.last})")
+ end
+ end
+
+ class AuthenticationRequired < StandardError
+ end
+
+ class APIError < StandardError
+ end
+
+ class InvalidLogin < StandardError
+ end
+
+ class RetryableAPIError < RuntimeError
+ attr_reader :code
+ def initialize(code=nil)
+ @code = code.nil? ? '???' : code
+ @message = "GitHub returned status #{@code}. Retrying request."
+ super @message
+ end
+ end
+
+ class ArgumentMustBeHash < Exception; end
+
+
+ class NotFound < Exception
+ def initialize(klass)
+ super "The #{klass.to_s.split("::").last} you were looking for could not be found, or is private."
+ end
+ end
+end
View
16 vendor/octopi-0.2.9/lib/octopi/file_object.rb
@@ -0,0 +1,16 @@
+module Octopi
+ class FileObject < Base
+ attr_accessor :name, :sha, :mode, :type
+
+ include Resource
+ set_resource_name "tree"
+ resource_path "/tree/show/:id"
+
+ def self.find(options={})
+ ensure_hash(options)
+ user, repo, branch, sha = gather_details(options)
+ self.validate_args(sha => :sha, user => :user, repo => :repo)
+ super [user, repo, sha]
+ end
+ end
+end
View
28 vendor/octopi-0.2.9/lib/octopi/gist.rb
@@ -0,0 +1,28 @@
+module Octopi
+ # Gist API is... lacking at the moment.
+ # This class serves only as a reminder to implement it later
+ class Gist < Base
+ include HTTParty
+ attr_accessor :description, :repo, :public, :created_at
+
+ include Resource
+ set_resource_name "tree"
+ resource_path ":id"
+
+ def self.base_uri
+ "http://gist.github.com/api/v1/yaml"
+ end
+
+ def self.find(id)
+ result = get("#{base_uri}/#{id}")
+ # This returns an array of Gists, rather than a single record.
+ new(result["gists"].first)
+ end
+
+ # def files
+ # gists_folder = File.join(ENV['HOME'], ".octopi", "gists")
+ # File.mkdir_p(gists_folder)
+ # `git clone git://`
+ # end
+ end
+end
View
111 vendor/octopi-0.2.9/lib/octopi/issue.rb
@@ -0,0 +1,111 @@
+module Octopi
+ class Issue < Base
+ include Resource
+ STATES = %w{open closed}
+
+ find_path "/issues/list/:query"
+ resource_path "/issues/show/:id"
+
+
+ attr_accessor :repository, :user, :updated_at, :votes, :number, :title, :body, :closed_at, :labels, :state, :created_at
+
+ def self.search(options={})
+ ensure_hash(options)
+ options[:state] ||= "open"
+ user, repo = gather_details(options)
+ Api.api.get("/issues/search/#{user}/#{repo}/#{options[:state]}/#{options[:keyword]}")
+ end
+
+ # Finds all issues for a given Repository
+ #
+ # You can provide the user and repo parameters as
+ # String or as User and Repository objects. When repo
+ # is provided as a Repository object, user is superfluous.
+ #
+ # If no state is given, "open" is assumed.
+ #
+ # Sample usage:
+ #
+ # find_all(repo, :state => "closed") # repo must be an object
+ # find_all("octopi", :user => "fcoury") # user must be provided
+ # find_all(:user => "fcoury", :repo => "octopi") # state defaults to open
+ #
+ def self.find_all(options={})
+ ensure_hash(options)
+ user, repo = gather_details(options)
+ state = (options[:state] || "open").downcase
+ validate_args(user => :user, repo.name => :repo, state => :state)
+
+ issues = super user, repo.name, state
+ issues.each { |i| i.repository = repo }
+ issues
+ end
+
+ # TODO: Make find use hashes like find_all
+ def self.find(options={})
+ ensure_hash(options)
+ # Do not cache issues, as they may change via other means.
+ @cache = false
+ user, repo = gather_details(options)
+
+ validate_args(user => :user, repo => :repo)
+ issue = super user, repo, options[:number]
+ issue.repository = repo
+ issue
+ end
+
+ def self.open(options={})
+ ensure_hash(options)
+ user, repo = gather_details(options)
+ data = Api.api.post("/issues/open/#{user}/#{repo.name}", options[:params])
+ issue = new(data['issue'])
+ issue.repository = repo
+ issue
+ end
+
+ # Re-opens an issue.
+ def reopen!
+ data = Api.api.post(command_path("reopen"))
+ self.state = 'open'
+ self
+ end
+
+ def close!
+ data = Api.api.post(command_path("close"))
+ self.state = 'closed'
+ self
+ end
+
+ def save
+ data = Api.api.post(command_path("edit"), { :title => title, :body => body })
+ self
+ end
+
+ %w(add remove).each do |oper|
+ define_method("#{oper}_label") do |*labels|
+ labels.each do |label|
+ Api.api.post("#{prefix("label/#{oper}")}/#{label}/#{number}", { :cache => false })
+ if oper == "add"
+ self.labels << label
+ else
+ self.labels -= [label]
+ end
+ end
+ end
+ end
+
+ def comment(comment)
+ data = Api.api.post(command_path("comment"), { :comment => comment })
+ IssueComment.new(data['comment'])
+ end
+
+ private
+ def prefix(command)
+ "/issues/#{command}/#{repository.owner}/#{repository.name}"
+ end
+
+ def command_path(command)
+ "#{prefix(command)}/#{number}"
+ end
+ end
+end
View
7 vendor/octopi-0.2.9/lib/octopi/issue_comment.rb
@@ -0,0 +1,7 @@
+module Octopi
+ class IssueComment < Base
+ include Resource
+ attr_accessor :comment, :status
+
+ end
+end
View
23 vendor/octopi-0.2.9/lib/octopi/issue_set.rb
@@ -0,0 +1,23 @@
+require File.join(File.dirname(__FILE__), "issue")
+class Octopi::IssueSet < Array
+ include Octopi
+ attr_accessor :user, :repository
+
+ def initialize(array)
+ unless array.empty?
+ self.user = array.first.user
+ self.repository = array.first.repository
+ super(array)
+ end
+ end
+
+ def find(number)
+ issue = detect { |issue| issue.number == number }
+ raise NotFound, Issue if issue.nil?
+ issue
+ end
+
+ def search(options={})
+ Issue.search(options.merge(:user => user, :repo => repository))
+ end
+end
View
25 vendor/octopi-0.2.9/lib/octopi/key.rb
@@ -0,0 +1,25 @@
+module Octopi
+ class Key < Base
+ include Resource
+
+ attr_accessor :title, :id, :key
+ find_path "/user/keys"
+
+ attr_reader :user
+
+ def self.find_all
+ Api.api.get("user/keys")
+ end
+
+ def self.add(options={})
+ ensure_hash(options)
+ Api.api.post("/user/key/add", { :title => options[:title], :key => options[:key], :cache => false })
+
+ end
+
+ def remove
+ result = Api.api.post "/user/key/remove", { :id => id, :cache => false }
+ keys = result["public_keys"].select { |k| k["title"] == title }
+ end
+ end
+end
View
14 vendor/octopi-0.2.9/lib/octopi/key_set.rb
@@ -0,0 +1,14 @@
+require File.join(File.dirname(__FILE__), "key")
+class KeySet < Array
+ include Octopi
+ def find(title)
+ key = detect { |key| key.title == title }
+ raise NotFound, Key if key.nil?
+ key
+ end
+
+ def add(options={})
+ ensure_hash(options)
+ Key.add(options)
+ end
+end
View
5 vendor/octopi-0.2.9/lib/octopi/plan.rb
@@ -0,0 +1,5 @@
+module Octopi
+ class Plan < Base
+ attr_accessor :name, :collaborators, :space, :private_repos
+ end
+end
View
132 vendor/octopi-0.2.9/lib/octopi/repository.rb
@@ -0,0 +1,132 @@
+module Octopi
+ class Repository < Base
+ include Resource
+ attr_accessor :description, :url, :forks, :name, :homepage, :watchers,
+ :owner, :private, :fork, :open_issues, :pledgie, :size,
+ # And now for the stuff returned by search results
+ :actions, :score, :language, :followers, :type, :username,
+ :id, :pushed, :created
+ set_resource_name "repository", "repositories"
+
+ create_path "/repos/create"
+ find_path "/repos/search/:query"
+ resource_path "/repos/show/:id"
+ delete_path "/repos/delete/:id"
+
+ attr_accessor :private
+
+ def owner=(owner)
+ @owner = User.find(owner)
+ end
+
+ # Returns all branches for the Repository
+ #
+ # Example:
+ # repo = Repository.find("fcoury", "octopi")
+ # repo.branches.each { |r| puts r.name }
+ #
+ def branches
+ Branch.all(:user => self.owner, :repo => self)
+ end
+
+ # Returns all tags for the Repository
+ #
+ # Example:
+ # repo = Repository.find("fcoury", "octopi")
+ # repo.tags.each { |t| puts t.name }
+ #
+ def tags
+ Tag.all(:user => self.owner, :repo => self)
+ end
+
+
+ # Returns all the comments for a Repository
+ def comments
+ # We have to specify xmlns as a prefix as the document is namespaced.
+ # Be wary!
+ path = "http#{'s' if private}://github.com/#{owner}/#{name}/comments.atom"
+ xml = Nokogiri::XML(Net::HTTP.get(URI.parse(path)))
+ entries = xml.xpath("//xmlns:entry")
+ comments = []
+ for entry in entries
+ content = entry.xpath("xmlns:content").text.gsub("&lt;", "<").gsub("&gt;", ">")
+ comments << Comment.new(
+ :id => entry.xpath("xmlns:id"),
+ :published => Time.parse(entry.xpath("xmlns:published").text),
+ :updated => Time.parse(entry.xpath("xmlns:updated").text),
+ :link => entry.xpath("xmlns:link/@href").text,
+ :title => entry.xpath("xmlns:title").text,
+ :content => content,
+ :author => entry.xpath("xmlns:author/xmlns:name").text,
+ :repository => self
+ )
+ end
+ comments
+ end
+
+ def clone_url
+ url = private || Api.api.login == self.owner.login ? "git@github.com:" : "git://github.com/"
+ url += "#{self.owner}/#{self.name}.git"
+ end
+
+ def self.find(options={})
+ ensure_hash(options)
+ # Lots of people call the same thing differently.
+ # Can't call gather_details here because this method is used by it internally.
+ repo = options[:repo] || options[:repository] || options[:name]
+ user = options[:user].to_s
+
+ return find_plural(user, :resource) if repo.nil?
+
+ self.validate_args(user => :user, repo => :repo)
+ super user, repo
+ end
+
+ def self.find_all(*args)
+ # FIXME: This should be URI escaped, but have to check how the API
+ # handles escaped characters first.
+ super args.join(" ").gsub(/ /,'+')
+ end
+
+ class << self
+ alias_method :search, :find_all
+ end
+
+ def commits(branch = "master")
+ Commit.find_all(:user => self.owner, :repo => self, :branch => branch)
+ end
+
+ def issues(state = "open")
+ IssueSet.new(Octopi::Issue.find_all(:user => owner, :repository => self, :state => state))
+ end
+
+ def all_issues
+ Issue::STATES.map{|state| self.issues(state)}.flatten
+ end
+
+ def issue(number)
+ Issue.find(:user => self.owner, :repo => self, :number => number)
+ end
+
+ def collaborators
+ property('collaborators', [self.owner, self.name].join('/')).values.map { |v| User.find(v.join) }
+ end
+
+ def self.create(options={})
+ raise AuthenticationRequired, "To create a repository you must be authenticated." if Api.api.read_only?
+ self.validate_args(options[:name] => :repo)
+ new(Api.api.post(path_for(:create), options)["repository"])
+ end
+
+ def delete!
+ raise APIError, "You must be authenticated as the owner of this repository to delete it" if Api.me.login != owner.login
+ token = Api.api.post(self.class.path_for(:delete), :id => self.name)['delete_token']
+ Api.api.post(self.class.path_for(:delete), :id => self.name, :delete_token => token) unless token.nil?
+ end
+
+ def to_s
+ name
+ end
+
+ end
+end
View
9 vendor/octopi-0.2.9/lib/octopi/repository_set.rb
@@ -0,0 +1,9 @@
+require File.join(File.dirname(__FILE__), "repository")
+class Octopi::RepositorySet < Array
+ include Octopi
+ attr_accessor :user
+
+ def find(name)
+ Octopi::Repository.find(:user => self.user, :repository => name)
+ end
+end
View
70 vendor/octopi-0.2.9/lib/octopi/resource.rb
@@ -0,0 +1,70 @@
+module Octopi
+ module Resource
+ def self.included(base)
+ base.extend ClassMethods
+ base.set_resource_name(base.name)
+ (@@resources||={})[base.resource_name(:singular)] = base
+ (@@resources||={})[base.resource_name(:plural)] = base
+ end
+
+ def self.for(name)
+ @@resources[name]
+ end
+
+ module ClassMethods
+ def set_resource_name(singular, plural = "#{singular}s")
+ @resource_name = {:singular => declassify(singular), :plural => declassify(plural)}
+ end
+
+ def resource_name(key)
+ @resource_name[key]
+ end
+
+ def create_path(path)
+ (@path_spec||={})[:create] = path
+ end
+
+ def find_path(path)
+ (@path_spec||={})[:find] = path
+ end
+
+ def resource_path(path)
+ (@path_spec||={})[:resource] = path
+ end
+
+ def delete_path(path)
+ (@path_spec||={})[:delete] = path
+ end
+
+ def find(*args)
+ args = args.join('/') if args.is_a? Array
+ result = Api.api.find(path_for(:resource), @resource_name[:singular], args, self, @cache)
+ key = result.keys.first
+
+ if result[key].is_a? Array
+ result[key].map { |r| new(r) }
+ else
+ Resource.for(key).new(result[key])
+ end
+ end
+
+ def find_all(*s)
+ find_plural(s, :find)
+ end
+
+ def find_plural(s, path)
+ s = s.join('/') if s.is_a? Array
+ resources = Api.api.find_all(path_for(path), @resource_name[:plural], s, self)
+ resources.map { |item| self.new(item) }
+ end
+
+ def declassify(s)
+ (s.split('::').last || '').downcase if s
+ end
+
+ def path_for(type)
+ @path_spec[type]
+ end
+ end
+ end
+end
View
33 vendor/octopi-0.2.9/lib/octopi/self.rb
@@ -0,0 +1,33 @@
+module Octopi
+ module Self
+ # Returns a list of Key objects containing all SSH Public Keys this user
+ # currently has. Requires authentication.
+ def keys
+ raise AuthenticationRequired, "To view keys, you must be authenticated" if Api.api.read_only?
+ result = Api.api.get("/user/keys", { :cache => false })
+ return unless result and result["public_keys"]
+ KeySet.new(result["public_keys"].inject([]) { |result, element| result << Key.new(element) })
+ end
+
+ # Returns a list of Email objects containing the email addresses associated with this user.
+ # Requires authentication.
+ def emails
+ raise AuthenticationRequired, "To view emails, you must be authenticated" if Api.api.read_only?
+ get("/user/emails")['emails']
+ end
+
+ # Start following a user.
+ # Can only be called if you are authenticated.
+ def follow!(login)
+ raise AuthenticationRequired, "To begin following someone, you must be authenticated" if Api.api.read_only?
+ Api.api.post("/user/follow/#{login}")
+ end
+
+ # Stop following a user.
+ # Can only be called if you are authenticated.
+ def unfollow!(login)
+ raise AuthenticationRequired, "To stop following someone, you must be authenticated" if Api.api.read_only?
+ Api.api.post("/user/unfollow/#{login}")
+ end
+ end
+end
View
23 vendor/octopi-0.2.9/lib/octopi/tag.rb
@@ -0,0 +1,23 @@
+module Octopi
+ class Tag < Base
+ include Resource
+
+ attr_accessor :name, :sha
+ set_resource_name "tag"
+
+ resource_path "/repos/show/:id"
+
+ def initialize(*args)
+ args = args.flatten!
+ self.name = args.first
+ self.sha = args.last
+ end
+
+ def self.all(options={})
+ ensure_hash(options)
+ user, repo = gather_details(options)
+ self.validate_args(user => :user, repo => :repo)
+ find_plural([user, repo, 'tags'], :resource) { |i| Tag.new(i) }
+ end
+ end
+end
View
131 vendor/octopi-0.2.9/lib/octopi/user.rb
@@ -0,0 +1,131 @@
+module Octopi
+ class User < Base
+ include Resource
+ attr_accessor :company, :name, :following_count, :gravatar_id,
+ :blog, :public_repo_count, :public_gist_count,
+ :id, :login, :followers_count, :created_at,
+ :email, :location, :disk_usage, :private_repo_count,
+ :private_gist_count, :collaborators, :plan,
+ :owned_private_repo_count, :total_private_repo_count,
+
+ # These come from search results, which doesn't
+ # contain the above information.
+ :actions, :score, :language, :followers, :following,
+ :fullname, :type, :username, :repos, :pushed, :created
+
+ def plan=(attributes={})
+ @plan = Plan.new(attributes)
+ end
+
+ find_path "/user/search/:query"
+ resource_path "/user/show/:id"
+
+ # Finds a single user identified by the given username
+ #
+ # Example:
+ #
+ # user = User.find("fcoury")
+ # puts user.login # should return 'fcoury'
+ def self.find(username)
+ self.validate_args(username => :user)
+ super username
+ end
+
+ # Finds all users whose username matches a given string
+ #
+ # Example:
+ #
+ # User.find_all("oe") # Matches joe, moe and monroe
+ #
+ def self.find_all(username)
+ self.validate_args(username => :user)
+ super username
+ end
+
+ class << self
+ alias_method :search, :find_all
+ end
+
+ # Returns a collection of Repository objects, containing
+ # all repositories of the user.
+ #
+ # If user is the current authenticated user, some
+ # additional information will be provided for the
+ # Repositories.
+ def repositories
+ rs = RepositorySet.new(Repository.find(:user => self.login))
+ rs.user = self
+ rs
+ end
+
+ # Searches for user Repository identified by name
+ def repository(options={})
+ options = { :name => options } if options.is_a?(String)
+ self.class.ensure_hash(options)
+ Repository.find({ :user => login }.merge!(options))
+ end
+
+ def create_repository(name, options = {})
+ self.class.validate_args(name => :repo)
+ Repository.create(self, name, options)
+ end
+
+ def watching
+ repositories = []
+ Api.api.get("/repos/watched/#{login}")["repositories"].each do |repo|
+ repositories << Repository.new(repo)
+ end
+ repositories
+ end
+
+
+ # Gets a list of followers.
+ # Returns an array of logins.
+ def followers
+ user_property("followers")
+ end
+
+ # Gets a list of followers.
+ # Returns an array of user objects.
+ # If user has a large number of followers you may be rate limited by the API.
+ def followers!
+ user_property("followers", true)
+ end
+
+ # Gets a list of people this user is following.
+ # Returns an array of logins.
+ def following
+ user_property("following")
+ end
+
+ # Gets a list of people this user is following.
+ # Returns an array of user objectrs.
+ # If user has a large number of people whom they follow, you may be rate limited by the API.
+ def following!
+ user_property("following", true)
+ end
+
+
+ # If a user object is passed into a method, we can use this.
+ # It'll also work if we pass in just the login.
+ def to_s
+ login
+ end
+
+ private
+
+ # Helper method for "deep" finds.
+ # Determines whether to return an array of logins (light) or user objects (heavy).
+ def user_property(property, deep=false)
+ users = []
+ property(property, login).each_pair do |k,v|
+ return v unless deep
+
+ v.each { |u| users << User.find(u) }
+ end
+
+ users
+ end
+
+ end
+end
View
106 vendor/octopi-0.2.9/octopi.gemspec
@@ -0,0 +1,106 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{octopi}
+ s.version = "0.2.9"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Felipe Coury"]
+ s.date = %q{2010-04-30}
+ s.email = %q{felipe.coury@gmail.com}
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "README.markdown"
+ ]
+ s.files = [
+ ".gitignore",
+ ".yardoc",
+ "CHANGELOG.md",
+ "LICENSE",
+ "README.markdown",
+ "Rakefile",
+ "VERSION.yml",
+ "contrib/backup.rb",
+ "lib/ext/string_ext.rb",
+ "lib/octopi.rb",
+ "lib/octopi/api.rb",
+ "lib/octopi/base.rb",
+ "lib/octopi/blob.rb",
+ "lib/octopi/branch.rb",
+ "lib/octopi/branch_set.rb",
+ "lib/octopi/comment.rb",
+ "lib/octopi/commit.rb",
+ "lib/octopi/error.rb",
+ "lib/octopi/file_object.rb",
+ "lib/octopi/gist.rb",
+ "lib/octopi/issue.rb",
+ "lib/octopi/issue_comment.rb",
+ "lib/octopi/issue_set.rb",
+ "lib/octopi/key.rb",
+ "lib/octopi/key_set.rb",
+ "lib/octopi/plan.rb",
+ "lib/octopi/repository.rb",
+ "lib/octopi/repository_set.rb",
+ "lib/octopi/resource.rb",
+ "lib/octopi/self.rb",
+ "lib/octopi/tag.rb",
+ "lib/octopi/user.rb",
+ "octopi.gemspec"
+ ]
+ s.homepage = %q{http://github.com/fcoury/octopi}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubyforge_project = %q{octopi}
+ s.rubygems_version = %q{1.3.6}
+ s.summary = %q{A Ruby interface to GitHub API v2}
+ s.test_files = [
+ "test/api_test.rb",
+ "test/authenticated_test.rb",
+ "test/base_test.rb",
+ "test/blob_test.rb",
+ "test/branch_test.rb",
+ "test/commit_test.rb",
+ "test/file_object_test.rb",
+ "test/gist_test.rb",
+ "test/issue_comment.rb",
+ "test/issue_set_test.rb",
+ "test/issue_test.rb",
+ "test/key_set_test.rb",
+ "test/key_test.rb",
+ "test/repository_set_test.rb",
+ "test/repository_test.rb",
+ "test/stubs/commits/fcoury/octopi/octopi.rb",
+ "test/tag_test.rb",
+ "test/test_helper.rb",
+ "test/user_test.rb",
+ "examples/authenticated.rb",
+ "examples/issues.rb",
+ "examples/overall.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.3.1"])
+ s.add_runtime_dependency(%q<httparty>, [">= 0.4.5"])
+ s.add_runtime_dependency(%q<mechanize>, [">= 0.9.3"])
+ s.add_runtime_dependency(%q<api_cache>, [">= 0"])
+ else
+ s.add_dependency(%q<nokogiri>, [">= 1.3.1"])
+ s.add_dependency(%q<httparty>, [">= 0.4.5"])
+ s.add_dependency(%q<mechanize>, [">= 0.9.3"])
+ s.add_dependency(%q<api_cache>, [">= 0"])
+ end
+ else
+ s.add_dependency(%q<nokogiri>, [">= 1.3.1"])
+ s.add_dependency(%q<httparty>, [">= 0.4.5"])
+ s.add_dependency(%q<mechanize>, [">= 0.9.3"])
+ s.add_dependency(%q<api_cache>, [">= 0"])
+ end
+end
+
View
58 vendor/octopi-0.2.9/test/api_test.rb
@@ -0,0 +1,58 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class AuthenticatedTest < Test::Unit::TestCase
+ include Octopi
+
+ def setup
+ fake_everything
+ @user = User.find("fcoury")
+ end
+
+ context "following" do
+
+ should "not be able to follow anyone if not authenticated" do
+ exception = assert_raise AuthenticationRequired do
+ Api.me.follow!("rails")
+ end
+ end
+
+ should "be able to follow a user" do
+ auth do
+ assert_not_nil Api.me.follow!("rails")
+ end
+ end
+ end
+
+ context "unfollowing" do
+
+ should "not be able to follow anyone if not authenticated" do
+ exception = assert_raise AuthenticationRequired do
+ Api.me.unfollow!("rails")
+ end
+ end
+
+ should "be able to follow a user" do
+ auth do
+ assert_not_nil Api.me.unfollow!("rails")
+ end
+ end
+ end
+
+ context "keys" do
+ should "not be able to see keys if not authenticated" do
+ exception = assert_raise AuthenticationRequired do
+ Api.me.keys
+ end
+
+ assert_equal "To view keys, you must be authenticated", exception.message
+ end
+
+ should "have some keys" do
+ auth do
+ keys = Api.me.keys
+ assert keys.is_a?(KeySet)
+ assert_equal 2, keys.size
+ end
+ end
+ end
+end
View
39 vendor/octopi-0.2.9/test/authenticated_test.rb
@@ -0,0 +1,39 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class AuthenticatedTest < Test::Unit::TestCase
+ include Octopi
+ def setup
+ fake_everything
+ end
+
+ context "Authenticating" do
+ should "be possible with username and password" do
+ authenticated_with(:login => "fcoury", :password => "yruocf") do
+ assert_equal "8f700e0d7747826f3e56ee13651414bd", Api.api.token
+ assert_not_nil User.find("fcoury")
+ end
+ end
+
+ should "be possible with username and token" do
+ auth do
+ assert_not_nil User.find("fcoury")
+ end
+ end
+
+ should "be possible using the .gitconfig" do
+ authenticated do
+ assert_not_nil User.find("fcoury")
+ end
+ end
+ #
+ # should "be denied access when specifying an invalid token and login combination" do
+ # FakeWeb.clean_registry
+ # FakeWeb.register_uri(:get, "http://github.com/api/v2/yaml/user/show/fcoury", :status => ["404", "Not Found"])
+ # assert_raise InvalidLogin do
+ # authenticated_with :login => "fcoury", :token => "ba7bf2d7f0ebc073d3874dda887b18ae" do
+ # # Just blank will do us fine.
+ # end
+ # end
+ # end
+ end
+end
View
20 vendor/octopi-0.2.9/test/base_test.rb
@@ -0,0 +1,20 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class BaseTest < Test::Unit::TestCase
+ class SparseUser < Octopi::Base
+ include Octopi::Resource
+
+ attr_accessor :some_attribute
+
+ find_path "/user/search/:query"
+ resource_path "/user/show/:id"
+ end
+
+ def setup
+ fake_everything
+ end
+
+ should "not raise an error if it doesn't know about the attributes that GitHub API provides" do
+ assert_nothing_raised { SparseUser.find("radar") }
+ end
+end
View
23 vendor/octopi-0.2.9/test/blob_test.rb
@@ -0,0 +1,23 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class BlobTest < Test::Unit::TestCase
+ include Octopi
+
+ def setup
+ fake_everything
+ end
+
+ context Blob do
+ should "find a blob" do
+ blob = Blob.find(:user => "fcoury", :repo => "octopi", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae")
+ assert_not_nil blob
+ assert blob.is_a?(String)
+ end
+
+ # Can't grab real data for this yet, Github is throwing a 500 on this request.
+ # should "find a file for a blob" do
+ # assert_not_nil Blob.find(:user => "fcoury", :repo => "octopi", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae", :path => "lib/octopi.rb")
+ # end
+ end
+end
+
View
20 vendor/octopi-0.2.9/test/branch_test.rb
@@ -0,0 +1,20 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class BranchTest < Test::Unit::TestCase
+ include Octopi
+
+ def setup
+ fake_everything
+ end
+
+ context Branch do
+ should "Find all branches for a repository" do
+ assert_not_nil Branch.all(:user => "fcoury", :name => "octopi")
+ end
+
+ should "Be able to find a specific branch" do
+ assert_not_nil Branch.all(:user => "fcoury", :name => "octopi").find("lazy")
+ end
+ end
+end
+
View
82 vendor/octopi-0.2.9/test/commit_test.rb
@@ -0,0 +1,82 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+
+class CommitTest < Test::Unit::TestCase
+ include Octopi
+
+ def setup
+ fake_everything
+ @user = User.find("fcoury")
+ @repo = @user.repository(:name => "octopi")
+ end
+
+ context Commit do
+ context "finding all commits" do
+ should "by strings" do
+ commits = Commit.find_all(:user => "fcoury", :repository => "octopi")
+ assert_not_nil commits
+ assert_equal 30, commits.size
+ assert_not_nil commits.first.repository
+ end
+
+ should "by objects" do
+ commits = Commit.find_all(:user => @user, :repository => @repo)
+ assert_not_nil commits
+ assert_equal 30, commits.size
+ end
+
+ should "be able to specify a branch" do
+ commits = Commit.find_all(:user => "fcoury", :repository => "octopi", :branch => "lazy")
+ assert_not_nil commits
+ assert_equal 30, commits.size
+ end
+
+ # Tests issue #28
+ should "be able to find commits in a private repository" do
+ auth do
+ commits = Commit.find_all(:user => "fcoury", :repository => "rboard")
+ end
+ assert_not_nil commits
+ assert_equal 22, commits.size
+ end
+
+ should "be able to find commits for a particular file" do
+ commits = Commit.find_all(:user => "fcoury", :repository => "octopi", :path => "lib/octopi.rb")
+ assert_not_nil commits
+ assert_equal 44, commits.size
+ end
+ end
+
+ context "finding a single commit" do
+ should "by strings" do
+ commit = Commit.find(:name => "octopi", :user => "fcoury", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae")
+ assert_not_nil commit