ddollar-octopi 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ module Octopi
2
+
3
+ class FormatError < StandardError
4
+ def initialize(f)
5
+ $stderr.puts "Got unexpected format (got #{f.first} for #{f.last})"
6
+ end
7
+ end
8
+
9
+ class APIError < StandardError
10
+ def initialize(m)
11
+ $stderr.puts m
12
+ end
13
+ end
14
+
15
+ class RetryableAPIError < RuntimeError
16
+ attr_reader :code
17
+ def initialize(code=nil)
18
+ @code = code.nil? ? '???' : code
19
+ @message = "GitHub returned status #{@code}. Retrying request."
20
+ super @message
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Octopi
2
+ class FileObject < Base
3
+ include Resource
4
+ set_resource_name "tree"
5
+
6
+ resource_path "/tree/show/:id"
7
+
8
+ def self.find(user, repo, sha)
9
+ user = user.login if user.is_a? User
10
+ repo = repo.name if repo.is_a? Repository
11
+ self.validate_args(sha => :sha, user => :user, repo => :repo)
12
+ super [user,repo,sha]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,102 @@
1
+ module Octopi
2
+ class Issue < Base
3
+ include Resource
4
+ STATES = %w{open closed}
5
+
6
+ find_path "/issues/list/:query"
7
+ resource_path "/issues/show/:id"
8
+
9
+ attr_accessor :repository
10
+
11
+ # Finds all issues for a given Repository
12
+ #
13
+ # You can provide the user and repo parameters as
14
+ # String or as User and Repository objects. When repo
15
+ # is provided as a Repository object, user is superfluous.
16
+ #
17
+ # If no state is given, "open" is assumed.
18
+ #
19
+ # Sample usage:
20
+ #
21
+ # find_all(repo, :state => "closed") # repo must be an object
22
+ # find_all("octopi", :user => "fcoury") # user must be provided
23
+ # find_all(:user => "fcoury", :repo => "octopi") # state defaults to open
24
+ #
25
+ def self.find_all(*args)
26
+ repo = args.first
27
+ user, repo_name, opts = extract_user_repository(*args)
28
+ state = opts[:state] || "open"
29
+ state.downcase! if state
30
+ validate_args(user => :user, repo_name => :repo, state => :state)
31
+
32
+ issues = super user, repo_name, state
33
+ issues.each { |i| i.repository = repo } if repo.is_a? Repository
34
+ issues
35
+ end
36
+
37
+ # TODO: Make find use hashes like find_all
38
+ def self.find(*args)
39
+ if args.length < 2
40
+ raise "Issue.find needs user, repository and issue number"
41
+ end
42
+
43
+ number = args.pop.to_i if args.last.respond_to?(:to_i)
44
+ number = args.pop if args.last.is_a?(Integer)
45
+
46
+ raise "Issue.find needs issue number as the last argument" unless number
47
+
48
+ if args.length > 1
49
+ user, repo = *args
50
+ else
51
+ repo = args.pop
52
+ raise "Issue.find needs at least a Repository object and issue number" unless repo.is_a? Repository
53
+ user, repo = repo.owner, repo.name
54
+ end
55
+
56
+ user, repo = extract_names(user, repo)
57
+ validate_args(user => :user, repo => :repo)
58
+ super user, repo, number
59
+ end
60
+
61
+ def self.open(user, repo, params, api = ANONYMOUS_API)
62
+ user, repo_name = extract_names(user, repo)
63
+ data = api.post("/issues/open/#{user}/#{repo_name}", params)
64
+ issue = new(api, data['issue'])
65
+ issue.repository = repo if repo.is_a? Repository
66
+ issue
67
+ end
68
+
69
+ def reopen(*args)
70
+ data = @api.post(command_path("reopen"))
71
+ end
72
+
73
+ def close(*args)
74
+ data = @api.post(command_path("close"))
75
+ end
76
+
77
+ def save
78
+ data = @api.post(command_path("edit"), { :title => self.title, :body => self.body })
79
+ end
80
+
81
+ %w(add remove).each do |oper|
82
+ define_method("#{oper}_label") do |*labels|
83
+ labels.each do |label|
84
+ @api.post("#{prefix("label/#{oper}")}/#{label}/#{number}")
85
+ end
86
+ end
87
+ end
88
+
89
+ def comment(comment)
90
+ @api.post(command_path("comment"), { :comment => comment })
91
+ end
92
+
93
+ private
94
+ def prefix(command)
95
+ "/issues/#{command}/#{repository.owner}/#{repository.name}"
96
+ end
97
+
98
+ def command_path(command)
99
+ "#{prefix(command)}/#{number}"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,18 @@
1
+ module Octopi
2
+ class Key < Base
3
+ include Resource
4
+
5
+ attr_reader :user
6
+
7
+ def initialize(api, data, user = nil)
8
+ super api, data
9
+ @user = user
10
+ end
11
+
12
+ def remove!
13
+ result = @api.post "/user/key/remove", :id => id
14
+ keys = result["public_keys"].select { |k| k["title"] == title }
15
+ keys.empty?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,111 @@
1
+ module Octopi
2
+ class Repository < Base
3
+ include Resource
4
+ set_resource_name "repository", "repositories"
5
+
6
+ create_path "/repos/create"
7
+ find_path "/repos/search/:query"
8
+ resource_path "/repos/show/:id"
9
+ delete_path "/repos/delete/:id"
10
+
11
+ attr_accessor :private
12
+
13
+ # Returns all branches for the Repository
14
+ #
15
+ # Example:
16
+ # repo = Repository.find("fcoury", "octopi")
17
+ # repo.branches.each { |r| puts r.name }
18
+ #
19
+ def branches
20
+ Branch.find(self.owner, self.name,api)
21
+ end
22
+
23
+ # Returns all tags for the Repository
24
+ #
25
+ # Example:
26
+ # repo = Repository.find("fcoury", "octopi")
27
+ # repo.tags.each { |t| puts t.name }
28
+ #
29
+ def tags
30
+ Tag.find(self.owner, self.name)
31
+ end
32
+
33
+ def clone_url
34
+ if private? || api.login == self.owner
35
+ "git@github.com:#{self.owner}/#{self.name}.git"
36
+ else
37
+ "git://github.com/#{self.owner}/#{self.name}.git"
38
+ end
39
+ end
40
+
41
+ def self.find_by_user(user, api = ANONYMOUS_API)
42
+ user = user.login if user.is_a? User
43
+ self.validate_args(user => :user)
44
+ find_plural(user, :resource, api)
45
+ end
46
+
47
+ def self.find(*args)
48
+ api = args.last.is_a?(Api) ? args.pop : ANONYMOUS_API
49
+ repo = args.pop
50
+ user = args.pop
51
+
52
+ user = user.login if user.is_a? User
53
+ if repo.is_a? Repository
54
+ repo = repo.name
55
+ user ||= repo.owner
56
+ end
57
+
58
+ self.validate_args(user => :user, repo => :repo)
59
+ super user, repo, api
60
+ end
61
+
62
+ def self.find_all(*args)
63
+ # FIXME: This should be URI escaped, but have to check how the API
64
+ # handles escaped characters first.
65
+ super args.join(" ").gsub(/ /,'+')
66
+ end
67
+
68
+ def self.open_issue(args)
69
+ Issue.open(args[:user], args[:repo], args)
70
+ end
71
+
72
+ def open_issue(args)
73
+ Issue.open(self.owner, self, args, @api)
74
+ end
75
+
76
+ def commits(branch = "master")
77
+ api = self.api || ANONYMOUS_API
78
+ Commit.find_all(self, {:branch => branch}, api)
79
+ end
80
+
81
+ def issues(state = "open")
82
+ Issue.find_all(self, :state => state)
83
+ end
84
+
85
+ def all_issues
86
+ Issue::STATES.map{|state| self.issues(state)}.flatten
87
+ end
88
+
89
+ def issue(number)
90
+ Issue.find(self, number)
91
+ end
92
+
93
+ def collaborators
94
+ property('collaborators', [self.owner,self.name].join('/')).values
95
+ end
96
+
97
+ def self.create(owner, name, opts = {})
98
+ api = owner.is_a?(User) ? owner.api : ANONYMOUS_API
99
+ raise APIError, "To create a repository you must be authenticated." if api.read_only?
100
+ self.validate_args(name => :repo)
101
+ api.post(path_for(:create), opts.merge(:name => name))
102
+ self.find(owner, name, api)
103
+ end
104
+
105
+ def delete
106
+ token = @api.post(self.class.path_for(:delete), :id => self.name)['delete_token']
107
+ @api.post(self.class.path_for(:delete), :id => self.name, :delete_token => token) unless token.nil?
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,75 @@
1
+ module Octopi
2
+ module Resource
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.set_resource_name(base.name)
6
+ (@@resources||={})[base.resource_name(:singular)] = base
7
+ (@@resources||={})[base.resource_name(:plural)] = base
8
+ end
9
+
10
+ def self.for(name)
11
+ @@resources[name]
12
+ end
13
+
14
+ module ClassMethods
15
+ def set_resource_name(singular, plural = "#{singular}s")
16
+ @resource_name = {:singular => declassify(singular), :plural => declassify(plural)}
17
+ end
18
+
19
+ def resource_name(key)
20
+ @resource_name[key]
21
+ end
22
+
23
+ def create_path(path)
24
+ (@path_spec||={})[:create] = path
25
+ end
26
+
27
+ def find_path(path)
28
+ (@path_spec||={})[:find] = path
29
+ end
30
+
31
+ def resource_path(path)
32
+ (@path_spec||={})[:resource] = path
33
+ end
34
+
35
+ def delete_path(path)
36
+ (@path_spec||={})[:delete] = path
37
+ end
38
+
39
+ def find(*args)
40
+ api = args.last.is_a?(Api) ? args.pop : ANONYMOUS_API
41
+ args = args.join('/') if args.is_a? Array
42
+ result = api.find(path_for(:resource), @resource_name[:singular], args)
43
+ key = result.keys.first
44
+
45
+ if result[key].is_a? Array
46
+ result[key].map { |r| new(api, r) }
47
+ else
48
+ Resource.for(key).new(api, result[key])
49
+ end
50
+ end
51
+
52
+ def find_all(*s)
53
+ api = s.last.is_a?(Api) ? s.pop : ANONYMOUS_API
54
+ find_plural(s, :find, api)
55
+ end
56
+
57
+ def find_plural(s, path, api = ANONYMOUS_API)
58
+ s = s.join('/') if s.is_a? Array
59
+ api.find_all(path_for(path), @resource_name[:plural], s).
60
+ map do |item|
61
+ payload = block_given? ? yield(item) : item
62
+ new(api, payload)
63
+ end
64
+ end
65
+
66
+ def declassify(s)
67
+ (s.split('::').last || '').downcase if s
68
+ end
69
+
70
+ def path_for(type)
71
+ @path_spec[type]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,17 @@
1
+ module Octopi
2
+ class Tag < Base
3
+ include Resource
4
+ set_resource_name "tag"
5
+
6
+ resource_path "/repos/show/:id"
7
+
8
+ def self.find(user, repo)
9
+ user = user.login if user.is_a? User
10
+ repo = repo.name if repo.is_a? Repository
11
+ self.validate_args(user => :user, repo => :repo)
12
+ find_plural([user,repo,'tags'], :resource){
13
+ |i| {:name => i.first, :hash => i.last }
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,99 @@
1
+ module Octopi
2
+ class User < Base
3
+ include Resource
4
+
5
+ find_path "/user/search/:query"
6
+ resource_path "/user/show/:id"
7
+
8
+ # Finds a single user identified by the given username
9
+ #
10
+ # Example:
11
+ #
12
+ # user = User.find("fcoury")
13
+ # puts user.login # should return 'fcoury'
14
+ def self.find(username)
15
+ self.validate_args(username => :user)
16
+ super username
17
+ end
18
+
19
+ # Finds all users whose username matches a given string
20
+ #
21
+ # Example:
22
+ #
23
+ # User.find_all("oe") # Matches joe, moe and monroe
24
+ #
25
+ def self.find_all(username)
26
+ self.validate_args(username => :user)
27
+ super username
28
+ end
29
+
30
+ # Returns a collection of Repository objects, containing
31
+ # all repositories of the user.
32
+ #
33
+ # If user is the current authenticated user, some
34
+ # additional information will be provided for the
35
+ # Repositories.
36
+ def repositories
37
+ api = self.api || ANONYMOUS_API
38
+ Repository.find_by_user(login,api)
39
+ end
40
+
41
+ # Searches for user Repository identified by
42
+ # name
43
+ def repository(name)
44
+ self.class.validate_args(name => :repo)
45
+ Repository.find(login, name)
46
+ end
47
+
48
+ def create_repository(name, opts = {})
49
+ self.class.validate_args(name => :repo)
50
+ Repository.create(self, name, opts)
51
+ end
52
+
53
+ # Adds an SSH Public Key to the user. Requires
54
+ # authentication.
55
+ def add_key(title, key)
56
+ raise APIError,
57
+ "To add a key, you must be authenticated" if @api.read_only?
58
+
59
+ result = @api.post("/user/key/add", :title => title, :key => key)
60
+ return if !result["public_keys"]
61
+ key_params = result["public_keys"].select { |k| k["title"] == title }
62
+ return if !key_params or key_params.empty?
63
+ Key.new(@api, key_params.first, self)
64
+ end
65
+
66
+ # Returns a list of Key objects containing all SSH Public Keys this user
67
+ # currently has. Requires authentication.
68
+ def keys
69
+ raise APIError,
70
+ "To add a key, you must be authenticated" if @api.read_only?
71
+
72
+ result = @api.get("/user/keys")
73
+ return unless result and result["public_keys"]
74
+ result["public_keys"].inject([]) { |result, element| result << Key.new(@api, element) }
75
+ end
76
+
77
+ # takes one param, deep that indicates if returns
78
+ # only the user login or an user object
79
+ %w[followers following].each do |method|
80
+ define_method(method) do
81
+ user_property(method, false)
82
+ end
83
+ define_method("#{method}!") do
84
+ user_property(method, true)
85
+ end
86
+ end
87
+
88
+ def user_property(property, deep)
89
+ users = []
90
+ property(property, login).each_pair do |k,v|
91
+ return v unless deep
92
+
93
+ v.each { |u| users << User.find(u) }
94
+ end
95
+
96
+ users
97
+ end
98
+ end
99
+ end