topfunky-github 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Chris Wanstrath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/github ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/github'
4
+
5
+ GitHub.activate ARGV
@@ -0,0 +1,127 @@
1
+ desc "Open this repo's master branch in a web browser."
2
+ command :home do |user|
3
+ if helper.project
4
+ helper.open helper.homepage_for(user || helper.owner, 'master')
5
+ end
6
+ end
7
+
8
+ desc "Automatically set configuration info, or pass args to specify."
9
+ usage "github [my_username] [my_repo_name]"
10
+ command :config do |user, repo|
11
+ user ||= ENV['USER']
12
+ repo ||= File.basename(FileUtils.pwd)
13
+ git "config --global github.user #{user}"
14
+ git "config github.repo #{repo}"
15
+ puts "Configured with github.user #{user}, github.repo #{repo}"
16
+ end
17
+
18
+ desc "Open this repo in a web browser."
19
+ usage "github [user] [branch]"
20
+ command :browse do |user, branch|
21
+ if helper.project
22
+ # if one arg given, treat it as a branch name
23
+ # unless it maches user/branch, then split it
24
+ # if two args given, treat as user branch
25
+ # if no args given, use defaults
26
+ user, branch = user.split("/", 2) if branch.nil? unless user.nil?
27
+ branch = user and user = nil if branch.nil?
28
+ user ||= helper.branch_user
29
+ branch ||= helper.branch_name
30
+ helper.open helper.homepage_for(user, branch)
31
+ end
32
+ end
33
+
34
+ desc "Open the network page for this repo in a web browser."
35
+ usage "github [user]"
36
+ command :network do |user|
37
+ if helper.project
38
+ user ||= helper.owner
39
+ helper.open helper.network_page_for(user)
40
+ end
41
+ end
42
+
43
+ desc "Info about this project."
44
+ command :info do
45
+ puts "== Info for #{helper.project}"
46
+ puts "You are #{helper.owner}"
47
+ puts "Currently tracking:"
48
+ helper.tracking.sort { |(a,),(b,)| a == :origin ? -1 : b == :origin ? 1 : a.to_s <=> b.to_s }.each do |(name,user_or_url)|
49
+ puts " - #{user_or_url} (as #{name})"
50
+ end
51
+ end
52
+
53
+ desc "Track another user's repository."
54
+ usage "github track remote [user]"
55
+ usage "github track remote [user/repo]"
56
+ usage "github track [user]"
57
+ usage "github track [user/repo]"
58
+ flags :private => "Use git@github.com: instead of git://github.com/."
59
+ flags :ssh => 'Equivalent to --private'
60
+ command :track do |remote, user|
61
+ # track remote user
62
+ # track remote user/repo
63
+ # track user
64
+ # track user/repo
65
+ user, remote = remote, nil if user.nil?
66
+ die "Specify a user to track" if user.nil?
67
+ user, repo = user.split("/", 2)
68
+ die "Already tracking #{user}" if helper.tracking?(user)
69
+ repo = @helper.project if repo.nil?
70
+ repo.chomp!(".git")
71
+ remote ||= user
72
+
73
+ if options[:private] || options[:ssh]
74
+ git "remote add #{remote} #{helper.private_url_for_user_and_repo(user, repo)}"
75
+ else
76
+ git "remote add #{remote} #{helper.public_url_for_user_and_repo(user, repo)}"
77
+ end
78
+ end
79
+
80
+ desc "Pull from a remote."
81
+ usage "github pull [user] [branch]"
82
+ flags :merge => "Automatically merge remote's changes into your master."
83
+ command :pull do |user, branch|
84
+ die "Specify a user to pull from" if user.nil?
85
+ user, branch = user.split("/", 2) if branch.nil?
86
+ branch ||= 'master'
87
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
88
+
89
+ if options[:merge]
90
+ git_exec "pull #{user} #{branch}"
91
+ else
92
+ puts "Switching to #{user}/#{branch}"
93
+ git "checkout #{user}/#{branch}" if git("checkout -b #{user}/#{branch}").error?
94
+ git_exec "pull #{user} #{branch}"
95
+ end
96
+ end
97
+
98
+ desc "Clone a repo."
99
+ usage "github clone [user] [repo] [dir]"
100
+ flags :ssh => "Clone using the git@github.com style url."
101
+ command :clone do |user, repo, dir|
102
+ die "Specify a user to pull from" if user.nil?
103
+ if user.include? ?/
104
+ die "Expected user/repo dir, given extra argument" if dir
105
+ (user, repo), dir = [user.split('/', 2), repo]
106
+ end
107
+ die "Specify a repo to pull from" if repo.nil?
108
+
109
+ if options[:ssh]
110
+ git_exec "clone git@github.com:#{user}/#{repo}.git" + (dir ? " #{dir}" : "")
111
+ else
112
+ git_exec "clone git://github.com/#{user}/#{repo}.git" + (dir ? " #{dir}" : "")
113
+ end
114
+ end
115
+
116
+ desc "Generate the text for a pull request."
117
+ usage "github pull-request [user] [branch]"
118
+ command :'pull-request' do |user, branch|
119
+ if helper.project
120
+ die "Specify a user for the pull request" if user.nil?
121
+ user, branch = user.split('/', 2) if branch.nil?
122
+ branch ||= 'master'
123
+ GitHub.invoke(:track, user) unless helper.tracking?(user)
124
+
125
+ git_exec "request-pull #{user}/#{branch} origin"
126
+ end
127
+ end
@@ -0,0 +1,123 @@
1
+ helper :user_and_repo_from do |url|
2
+ case url
3
+ when %r|^git://github\.com/([^/]+/[^/]+)$|: $1.split('/')
4
+ when %r|^(?:ssh://)?(?:git@)?github\.com:([^/]+/[^/]+)$|: $1.split('/')
5
+ end
6
+ end
7
+
8
+ helper :user_and_repo_for do |remote|
9
+ user_and_repo_from(url_for(remote))
10
+ end
11
+
12
+ helper :user_for do |remote|
13
+ user_and_repo_for(remote).try.first
14
+ end
15
+
16
+ helper :repo_for do |remote|
17
+ user_and_repo_for(remote).try.last
18
+ end
19
+
20
+ helper :project do
21
+ repo = repo_for(:origin)
22
+ if repo.nil?
23
+ if url_for(:origin) == ""
24
+ STDERR.puts "Error: missing remote 'origin'"
25
+ else
26
+ STDERR.puts "Error: remote 'origin' is not a github URL"
27
+ end
28
+ exit 1
29
+ end
30
+ repo.chomp('.git')
31
+ end
32
+
33
+ helper :url_for do |remote|
34
+ `git config --get remote.#{remote}.url`.chomp
35
+ end
36
+
37
+ helper :remotes do
38
+ regexp = '^remote\.(.+)\.url$'
39
+ `git config --get-regexp '#{regexp}'`.split(/\n/).inject({}) do |memo, line|
40
+ name_string, url = line.split(/ /, 2)
41
+ m, name = *name_string.match(/#{regexp}/)
42
+ memo[name.to_sym] = url
43
+ memo
44
+ end
45
+ end
46
+
47
+ helper :tracking do
48
+ remotes.inject({}) do |memo, (name, url)|
49
+ if ur = user_and_repo_from(url)
50
+ memo[name] = ur.first
51
+ else
52
+ memo[name] = url
53
+ end
54
+ memo
55
+ end
56
+ end
57
+
58
+ helper :tracking? do |user|
59
+ tracking.values.include?(user)
60
+ end
61
+
62
+ helper :owner do
63
+ user_for(:origin)
64
+ end
65
+
66
+ helper :user_and_branch do
67
+ raw_branch = `git rev-parse --symbolic-full-name HEAD`.chomp.sub(/^refs\/heads\//, '')
68
+ user, branch = raw_branch.split(/\//, 2)
69
+ if branch
70
+ [user, branch]
71
+ else
72
+ [owner, user]
73
+ end
74
+ end
75
+
76
+ helper :branch_user do
77
+ user_and_branch.first
78
+ end
79
+
80
+ helper :branch_name do
81
+ user_and_branch.last
82
+ end
83
+
84
+ helper :public_url_for_user_and_repo do |user, repo|
85
+ "git://github.com/#{user}/#{repo}.git"
86
+ end
87
+
88
+ helper :private_url_for_user_and_repo do |user, repo|
89
+ "git@github.com:#{user}/#{repo}.git"
90
+ end
91
+
92
+ helper :public_url_for do |user|
93
+ public_url_for_user_and_repo user, project
94
+ end
95
+
96
+ helper :private_url_for do |user|
97
+ private_url_for_user_and_repo user, project
98
+ end
99
+
100
+ helper :homepage_for do |user, branch|
101
+ "https://github.com/#{user}/#{project}/tree/#{branch}"
102
+ end
103
+
104
+ helper :network_page_for do |user|
105
+ "https://github.com/#{user}/#{project}/network"
106
+ end
107
+
108
+ helper :has_launchy? do |blk|
109
+ begin
110
+ gem 'launchy'
111
+ require 'launchy'
112
+ blk.call
113
+ rescue Gem::LoadError
114
+ STDERR.puts "Sorry, you need to install launchy: `gem install launchy`"
115
+ end
116
+ end
117
+
118
+ helper :open do |url|
119
+ has_launchy? proc {
120
+ Launchy::Browser.new.visit url
121
+ }
122
+ end
123
+
data/lib/github.rb ADDED
@@ -0,0 +1,145 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'github/extensions'
3
+ require 'github/command'
4
+ require 'github/helper'
5
+ require 'fileutils'
6
+ require 'rubygems'
7
+
8
+ ##
9
+ # Starting simple.
10
+ #
11
+ # $ github <command> <args>
12
+ #
13
+ # GitHub.command <command> do |*args|
14
+ # whatever
15
+ # end
16
+ #
17
+ # We'll probably want to use the `choice` gem for concise, tasty DSL
18
+ # arg parsing action.
19
+ #
20
+
21
+ module GitHub
22
+ extend self
23
+
24
+ BasePath = File.expand_path(File.dirname(__FILE__) + '/..')
25
+
26
+ def command(command, &block)
27
+ debug "Registered `#{command}`"
28
+ descriptions[command] = @next_description if @next_description
29
+ @next_description = nil
30
+ flag_descriptions[command].update @next_flags if @next_flags
31
+ usage_descriptions[command] = @next_usage if @next_usage
32
+ @next_flags = nil
33
+ @next_usage = []
34
+ commands[command.to_s] = Command.new(block)
35
+ end
36
+
37
+ def desc(str)
38
+ @next_description = str
39
+ end
40
+
41
+ def flags(hash)
42
+ @next_flags ||= {}
43
+ @next_flags.update hash
44
+ end
45
+
46
+ def usage(string)
47
+ @next_usage ||= []
48
+ @next_usage << string
49
+ end
50
+
51
+ def helper(command, &block)
52
+ debug "Helper'd `#{command}`"
53
+ Helper.send :define_method, command, &block
54
+ end
55
+
56
+ def activate(args)
57
+ @options = parse_options(args)
58
+ @debug = @options[:debug]
59
+ load 'helpers.rb'
60
+ load 'commands.rb'
61
+ invoke(args.shift, *args)
62
+ end
63
+
64
+ def invoke(command, *args)
65
+ block = commands[command.to_s] || commands['default']
66
+ debug "Invoking `#{command}`"
67
+ block.call(*args)
68
+ end
69
+
70
+ def commands
71
+ @commands ||= {}
72
+ end
73
+
74
+ def descriptions
75
+ @descriptions ||= {}
76
+ end
77
+
78
+ def flag_descriptions
79
+ @flagdescs ||= Hash.new { |h, k| h[k] = {} }
80
+ end
81
+
82
+ def usage_descriptions
83
+ @usage_descriptions ||= Hash.new { |h, k| h[k] = [] }
84
+ end
85
+
86
+ def options
87
+ @options
88
+ end
89
+
90
+ def parse_options(args)
91
+ idx = 0
92
+ args.clone.inject({}) do |memo, arg|
93
+ case arg
94
+ when /^--(.+?)=(.*)/
95
+ args.delete_at(idx)
96
+ memo.merge($1.to_sym => $2)
97
+ when /^--(.+)/
98
+ args.delete_at(idx)
99
+ memo.merge($1.to_sym => true)
100
+ when "--"
101
+ args.delete_at(idx)
102
+ return memo
103
+ else
104
+ idx += 1
105
+ memo
106
+ end
107
+ end
108
+ end
109
+
110
+ def load(file)
111
+ file[0] == ?/ ? path = file : path = BasePath + "/commands/#{file}"
112
+ data = File.read(path)
113
+ GitHub.module_eval data, path
114
+ end
115
+
116
+ def debug(*messages)
117
+ puts *messages.map { |m| "== #{m}" } if debug?
118
+ end
119
+
120
+ def debug?
121
+ !!@debug
122
+ end
123
+ end
124
+
125
+ GitHub.command :default do
126
+ puts "Usage: github command <space separated arguments>", ''
127
+ puts "Available commands:", ''
128
+ longest = GitHub.descriptions.map { |d,| d.to_s.size }.max
129
+ GitHub.descriptions.sort {|a,b| a.to_s <=> b.to_s }.each do |command, desc|
130
+ cmdstr = "%-#{longest}s" % command
131
+ puts " #{cmdstr} => #{desc}"
132
+ flongest = GitHub.flag_descriptions[command].map { |d,| "--#{d}".size }.max
133
+ GitHub.usage_descriptions[command].each do |usage_descriptions|
134
+ usage_descriptions.each do |usage|
135
+ usage_str = "#{" " * longest} %% %-#{flongest}s" % usage
136
+ puts usage_str
137
+ end
138
+ end
139
+ GitHub.flag_descriptions[command].each do |flag, fdesc|
140
+ flagstr = "#{" " * longest} %-#{flongest}s" % "--#{flag}"
141
+ puts " #{flagstr}: #{fdesc}"
142
+ end
143
+ end
144
+ puts
145
+ end
@@ -0,0 +1,87 @@
1
+ if RUBY_PLATFORM =~ /mswin|mingw/
2
+ begin
3
+ require 'win32/open3'
4
+ rescue LoadError
5
+ warn "You must 'gem install win32-open3' to use the github command on Windows"
6
+ exit 1
7
+ end
8
+ else
9
+ require 'open3'
10
+ end
11
+
12
+ module GitHub
13
+ class Command
14
+ def initialize(block)
15
+ (class << self;self end).send :define_method, :command, &block
16
+ end
17
+
18
+ def call(*args)
19
+ arity = method(:command).arity
20
+ args << nil while args.size < arity
21
+ send :command, *args
22
+ end
23
+
24
+ def helper
25
+ @helper ||= Helper.new
26
+ end
27
+
28
+ def options
29
+ GitHub.options
30
+ end
31
+
32
+ def pgit(*command)
33
+ puts git(*command)
34
+ end
35
+
36
+ def git(*command)
37
+ sh ['git', command].flatten.join(' ')
38
+ end
39
+
40
+ def git_exec(*command)
41
+ cmdstr = ['git', command].flatten.join(' ')
42
+ GitHub.debug "exec: #{cmdstr}"
43
+ exec cmdstr
44
+ end
45
+
46
+ def sh(*command)
47
+ Shell.new(*command).run
48
+ end
49
+
50
+ def die(message)
51
+ puts "=> #{message}"
52
+ exit!
53
+ end
54
+
55
+ class Shell < String
56
+ def initialize(*command)
57
+ @command = command
58
+ end
59
+
60
+ def run
61
+ GitHub.debug "sh: #{command}"
62
+ _, out, err = Open3.popen3(*@command)
63
+
64
+ out = out.read.strip
65
+ err = err.read.strip
66
+
67
+ if out.any?
68
+ replace @out = out
69
+ elsif err.any?
70
+ replace @error = err
71
+ end
72
+ end
73
+
74
+ def command
75
+ @command.join(' ')
76
+ end
77
+
78
+ def error?
79
+ !!@error
80
+ end
81
+
82
+ def out?
83
+ !!@out
84
+ end
85
+ end
86
+ end
87
+ end