bitprophet-github 0.3.4
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.
- data/LICENSE +18 -0
- data/Manifest +23 -0
- data/README +164 -0
- data/bin/gh +8 -0
- data/bin/github +8 -0
- data/github-gem.gemspec +27 -0
- data/lib/commands/commands.rb +206 -0
- data/lib/commands/helpers.rb +397 -0
- data/lib/commands/network.rb +113 -0
- data/lib/github.rb +183 -0
- data/lib/github/command.rb +129 -0
- data/lib/github/extensions.rb +39 -0
- data/lib/github/helper.rb +4 -0
- data/spec/command_spec.rb +82 -0
- data/spec/extensions_spec.rb +36 -0
- data/spec/github_spec.rb +85 -0
- data/spec/helper_spec.rb +280 -0
- data/spec/spec_helper.rb +138 -0
- data/spec/ui_spec.rb +604 -0
- data/spec/windoze_spec.rb +36 -0
- metadata +105 -0
data/lib/github.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'github/extensions'
|
3
|
+
require 'github/command'
|
4
|
+
require 'github/helper'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'json'
|
9
|
+
require 'yaml'
|
10
|
+
require 'text/format'
|
11
|
+
|
12
|
+
##
|
13
|
+
# Starting simple.
|
14
|
+
#
|
15
|
+
# $ github <command> <args>
|
16
|
+
#
|
17
|
+
# GitHub.command <command> do |*args|
|
18
|
+
# whatever
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
|
22
|
+
module GitHub
|
23
|
+
extend self
|
24
|
+
|
25
|
+
BasePath = File.expand_path(File.dirname(__FILE__))
|
26
|
+
|
27
|
+
def command(command, options = {}, &block)
|
28
|
+
command = command.to_s
|
29
|
+
debug "Registered `#{command}`"
|
30
|
+
descriptions[command] = @next_description if @next_description
|
31
|
+
@next_description = nil
|
32
|
+
flag_descriptions[command].update @next_flags if @next_flags
|
33
|
+
usage_descriptions[command] = @next_usage if @next_usage
|
34
|
+
@next_flags = nil
|
35
|
+
@next_usage = []
|
36
|
+
commands[command] = Command.new(block)
|
37
|
+
Array(options[:alias] || options[:aliases]).each do |command_alias|
|
38
|
+
commands[command_alias.to_s] = commands[command.to_s]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def desc(str)
|
43
|
+
@next_description = str
|
44
|
+
end
|
45
|
+
|
46
|
+
def flags(hash)
|
47
|
+
@next_flags ||= {}
|
48
|
+
@next_flags.update hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def usage(string)
|
52
|
+
@next_usage ||= []
|
53
|
+
@next_usage << string
|
54
|
+
end
|
55
|
+
|
56
|
+
def helper(command, &block)
|
57
|
+
debug "Helper'd `#{command}`"
|
58
|
+
Helper.send :define_method, command, &block
|
59
|
+
end
|
60
|
+
|
61
|
+
def activate(args)
|
62
|
+
@@original_args = args.clone
|
63
|
+
@options = parse_options(args)
|
64
|
+
@debug = @options.delete(:debug)
|
65
|
+
@learn = @options.delete(:learn)
|
66
|
+
Dir[BasePath + '/commands/*.rb'].each do |command|
|
67
|
+
load command
|
68
|
+
end
|
69
|
+
invoke(args.shift, *args)
|
70
|
+
end
|
71
|
+
|
72
|
+
def invoke(command, *args)
|
73
|
+
block = find_command(command)
|
74
|
+
debug "Invoking `#{command}`"
|
75
|
+
block.call(*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_command(name)
|
79
|
+
name = name.to_s
|
80
|
+
commands[name] || GitCommand.new(name) || commands['default']
|
81
|
+
end
|
82
|
+
|
83
|
+
def commands
|
84
|
+
@commands ||= {}
|
85
|
+
end
|
86
|
+
|
87
|
+
def descriptions
|
88
|
+
@descriptions ||= {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def flag_descriptions
|
92
|
+
@flagdescs ||= Hash.new { |h, k| h[k] = {} }
|
93
|
+
end
|
94
|
+
|
95
|
+
def usage_descriptions
|
96
|
+
@usage_descriptions ||= Hash.new { |h, k| h[k] = [] }
|
97
|
+
end
|
98
|
+
|
99
|
+
def options
|
100
|
+
@options
|
101
|
+
end
|
102
|
+
|
103
|
+
def original_args
|
104
|
+
@@original_args ||= []
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse_options(args)
|
108
|
+
idx = 0
|
109
|
+
args.clone.inject({}) do |memo, arg|
|
110
|
+
case arg
|
111
|
+
when /^--(.+?)=(.*)/
|
112
|
+
args.delete_at(idx)
|
113
|
+
memo.merge($1.to_sym => $2)
|
114
|
+
when /^--(.+)/
|
115
|
+
args.delete_at(idx)
|
116
|
+
memo.merge($1.to_sym => true)
|
117
|
+
when "--"
|
118
|
+
args.delete_at(idx)
|
119
|
+
return memo
|
120
|
+
else
|
121
|
+
idx += 1
|
122
|
+
memo
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def debug(*messages)
|
128
|
+
puts *messages.map { |m| "== #{m}" } if debug?
|
129
|
+
end
|
130
|
+
|
131
|
+
def learn(message)
|
132
|
+
if learn?
|
133
|
+
puts "== " + Color.yellow(message)
|
134
|
+
else
|
135
|
+
debug(message)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def learn?
|
140
|
+
!!@learn
|
141
|
+
end
|
142
|
+
|
143
|
+
def debug?
|
144
|
+
!!@debug
|
145
|
+
end
|
146
|
+
|
147
|
+
def load(file)
|
148
|
+
file[0] =~ /^\// ? path = file : path = BasePath + "/commands/#{File.basename(file)}"
|
149
|
+
data = File.read(path)
|
150
|
+
GitHub.module_eval data, path
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
GitHub.command :default, :aliases => ['', '-h', 'help', '-help', '--help'] do
|
155
|
+
puts "Usage: github command <space separated arguments>", ''
|
156
|
+
puts "Available commands:", ''
|
157
|
+
longest = GitHub.descriptions.map { |d,| d.to_s.size }.max
|
158
|
+
indent = longest + 6 # length of " " + " => "
|
159
|
+
fmt = Text::Format.new(
|
160
|
+
:first_indent => indent,
|
161
|
+
:body_indent => indent,
|
162
|
+
:columns => 79 # be a little more lenient than the default
|
163
|
+
)
|
164
|
+
GitHub.descriptions.sort {|a,b| a.to_s <=> b.to_s }.each do |command, desc|
|
165
|
+
cmdstr = "%-#{longest}s" % command
|
166
|
+
desc = fmt.format(desc).strip # strip to eat first "indent"
|
167
|
+
puts " #{cmdstr} => #{desc}"
|
168
|
+
flongest = GitHub.flag_descriptions[command].map { |d,| "--#{d}".size }.max
|
169
|
+
ffmt = fmt.clone
|
170
|
+
ffmt.body_indent += 2 # length of "% " and/or "--"
|
171
|
+
GitHub.usage_descriptions[command].each do |usage_descriptions|
|
172
|
+
usage_descriptions.each do |usage|
|
173
|
+
usage_str = "%% %-#{flongest}s" % usage
|
174
|
+
puts ffmt.format(usage_str)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
GitHub.flag_descriptions[command].each do |flag, fdesc|
|
178
|
+
flagstr = "#{" " * longest} %-#{flongest}s" % "--#{flag}"
|
179
|
+
puts ffmt.format(" #{flagstr}: #{fdesc}")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
puts
|
183
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM =~ /mswin|mingw/
|
4
|
+
begin
|
5
|
+
require 'win32/open3'
|
6
|
+
rescue LoadError
|
7
|
+
warn "You must 'gem install win32-open3' to use the github command on Windows"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
else
|
11
|
+
require 'open3'
|
12
|
+
end
|
13
|
+
|
14
|
+
module GitHub
|
15
|
+
class Command
|
16
|
+
include FileUtils
|
17
|
+
|
18
|
+
def initialize(block)
|
19
|
+
(class << self;self end).send :define_method, :command, &block
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(*args)
|
23
|
+
arity = method(:command).arity
|
24
|
+
args << nil while args.size < arity
|
25
|
+
send :command, *args
|
26
|
+
end
|
27
|
+
|
28
|
+
def helper
|
29
|
+
@helper ||= Helper.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def options
|
33
|
+
GitHub.options
|
34
|
+
end
|
35
|
+
|
36
|
+
def pgit(*command)
|
37
|
+
puts git(*command)
|
38
|
+
end
|
39
|
+
|
40
|
+
def git(command)
|
41
|
+
run :sh, command
|
42
|
+
end
|
43
|
+
|
44
|
+
def git_exec(command)
|
45
|
+
run :exec, command
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(method, command)
|
49
|
+
if command.is_a? Array
|
50
|
+
command = [ 'git', command ].flatten
|
51
|
+
GitHub.learn command.join(' ')
|
52
|
+
else
|
53
|
+
command = 'git ' + command
|
54
|
+
GitHub.learn command
|
55
|
+
end
|
56
|
+
|
57
|
+
send method, *command
|
58
|
+
end
|
59
|
+
|
60
|
+
def sh(*command)
|
61
|
+
Shell.new(*command).run
|
62
|
+
end
|
63
|
+
|
64
|
+
def die(message)
|
65
|
+
puts "=> #{message}"
|
66
|
+
exit!
|
67
|
+
end
|
68
|
+
|
69
|
+
def github_user
|
70
|
+
git("config --get github.user")
|
71
|
+
end
|
72
|
+
|
73
|
+
def github_token
|
74
|
+
git("config --get github.token")
|
75
|
+
end
|
76
|
+
|
77
|
+
def shell_user
|
78
|
+
ENV['USER']
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_user?(user)
|
82
|
+
user == github_user || user == shell_user
|
83
|
+
end
|
84
|
+
|
85
|
+
class Shell < String
|
86
|
+
attr_reader :error
|
87
|
+
attr_reader :out
|
88
|
+
|
89
|
+
def initialize(*command)
|
90
|
+
@command = command
|
91
|
+
end
|
92
|
+
|
93
|
+
def run
|
94
|
+
GitHub.debug "sh: #{command}"
|
95
|
+
_, out, err = Open3.popen3(*@command)
|
96
|
+
|
97
|
+
out = out.read.strip
|
98
|
+
err = err.read.strip
|
99
|
+
|
100
|
+
replace @error = err if err.any?
|
101
|
+
replace @out = out if out.any?
|
102
|
+
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def command
|
107
|
+
@command.join(' ')
|
108
|
+
end
|
109
|
+
|
110
|
+
def error?
|
111
|
+
!!@error
|
112
|
+
end
|
113
|
+
|
114
|
+
def out?
|
115
|
+
!!@out
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class GitCommand < Command
|
121
|
+
def initialize(name)
|
122
|
+
@name = name
|
123
|
+
end
|
124
|
+
|
125
|
+
def command(*args)
|
126
|
+
git_exec [ @name, args ]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# define #try
|
2
|
+
class Object
|
3
|
+
def try
|
4
|
+
self
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class NilClass
|
9
|
+
klass = Class.new
|
10
|
+
klass.class_eval do
|
11
|
+
instance_methods.each { |meth| undef_method meth.to_sym unless meth =~ /^__(id|send)__$/ }
|
12
|
+
def method_missing(*args)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
NilProxy = klass.new
|
17
|
+
def try
|
18
|
+
NilProxy
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# define #tap
|
23
|
+
class Object
|
24
|
+
def tap(&block)
|
25
|
+
block.call(self)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# cute
|
31
|
+
module Color
|
32
|
+
COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 }
|
33
|
+
def self.method_missing(color_name, *args)
|
34
|
+
color(color_name) + args.first + color(:clear)
|
35
|
+
end
|
36
|
+
def self.color(color)
|
37
|
+
"\e[#{COLORS[color.to_sym]}m"
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe GitHub::Command do
|
4
|
+
before(:each) do
|
5
|
+
@command = GitHub::Command.new(proc { |x| puts x })
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should return a GitHub::Helper" do
|
9
|
+
@command.helper.should be_instance_of(GitHub::Helper)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should call successfully" do
|
13
|
+
@command.should_receive(:puts).with("test").once
|
14
|
+
@command.call("test")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return options" do
|
18
|
+
GitHub.should_receive(:options).with().once.and_return({:ssh => true})
|
19
|
+
@command.options.should == {:ssh => true}
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should successfully call out to the shell" do
|
23
|
+
unguard(Kernel, :fork)
|
24
|
+
unguard(Kernel, :exec)
|
25
|
+
hi = @command.sh("echo hi")
|
26
|
+
hi.should == "hi"
|
27
|
+
hi.out.should == "hi"
|
28
|
+
hi.out?.should be(true)
|
29
|
+
hi.error.should be_nil
|
30
|
+
hi.error?.should be(false)
|
31
|
+
hi.command.should == "echo hi"
|
32
|
+
if RUBY_PLATFORM =~ /mingw|mswin/
|
33
|
+
command = "cmd /c echo bye >&2"
|
34
|
+
else
|
35
|
+
command = "echo bye >&2"
|
36
|
+
end
|
37
|
+
bye = @command.sh(command)
|
38
|
+
bye.should == "bye"
|
39
|
+
bye.out.should be_nil
|
40
|
+
bye.out?.should be(false)
|
41
|
+
bye.error.should == "bye"
|
42
|
+
bye.error?.should be(true)
|
43
|
+
bye.command.should == command
|
44
|
+
hi_and_bye = @command.sh("echo hi; echo bye >&2")
|
45
|
+
hi_and_bye.should == "hi"
|
46
|
+
hi_and_bye.out.should == "hi"
|
47
|
+
hi_and_bye.out?.should be(true)
|
48
|
+
hi_and_bye.error.should == "bye"
|
49
|
+
hi_and_bye.error?.should be(true)
|
50
|
+
hi_and_bye.command.should == "echo hi; echo bye >&2"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return the results of a git operation" do
|
54
|
+
GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
|
55
|
+
s = mock("GitHub::Commands::Shell")
|
56
|
+
s.should_receive(:run).once.and_return("sha1")
|
57
|
+
s
|
58
|
+
end
|
59
|
+
@command.git("rev-parse master").should == "sha1"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should print the results of a git operation" do
|
63
|
+
@command.should_receive(:puts).with("sha1").once
|
64
|
+
GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
|
65
|
+
s = mock("GitHub::Commands::Shell")
|
66
|
+
s.should_receive(:run).once.and_return("sha1")
|
67
|
+
s
|
68
|
+
end
|
69
|
+
@command.pgit("rev-parse master")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should exec a git command" do
|
73
|
+
@command.should_receive(:exec).with("git rev-parse master").once
|
74
|
+
@command.git_exec "rev-parse master"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should die" do
|
78
|
+
@command.should_receive(:puts).once.with("=> message")
|
79
|
+
@command.should_receive(:exit!).once
|
80
|
+
@command.die "message"
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe "When calling #try" do
|
4
|
+
specify "objects should return themselves" do
|
5
|
+
obj = 1; obj.try.should equal(obj)
|
6
|
+
obj = "foo"; obj.try.should equal(obj)
|
7
|
+
obj = { :foo => "bar" }; obj.try.should equal(obj)
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "objects should behave as if #try wasn't called" do
|
11
|
+
"foo".try.size.should == 3
|
12
|
+
{ :foo => :bar }.try.fetch(:foo).should == :bar
|
13
|
+
[1, 2, 3].try.map { |x| x + 1 }.should == [2, 3, 4]
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "nil should return the singleton NilClass::NilProxy" do
|
17
|
+
nil.try.should equal(NilClass::NilProxy)
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "nil should ignore any calls made past #try" do
|
21
|
+
nil.try.size.should equal(NilClass::NilProxy)
|
22
|
+
nil.try.sdlfj.should equal(NilClass::NilProxy)
|
23
|
+
nil.try.one.two.three.should equal(NilClass::NilProxy)
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "classes should respond just like objects" do
|
27
|
+
String.try.should equal(String)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "When calling #tap" do
|
32
|
+
specify "objects should behave like Ruby 1.9's #tap" do
|
33
|
+
obj = "foo"
|
34
|
+
obj.tap { |obj| obj.size.should == 3 }.should equal(obj)
|
35
|
+
end
|
36
|
+
end
|