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/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,4 @@
1
+ module GitHub
2
+ class Helper
3
+ end
4
+ 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