git-style-binaries 0.1.10
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/README.markdown +280 -0
- data/Rakefile +64 -0
- data/VERSION.yml +4 -0
- data/lib/ext/colorize.rb +198 -0
- data/lib/ext/core.rb +16 -0
- data/lib/git-style-binary.rb +88 -0
- data/lib/git-style-binary/autorunner.rb +21 -0
- data/lib/git-style-binary/command.rb +204 -0
- data/lib/git-style-binary/commands/help.rb +32 -0
- data/lib/git-style-binary/helpers/name_resolver.rb +78 -0
- data/lib/git-style-binary/helpers/pager.rb +37 -0
- data/lib/git-style-binary/parser.rb +223 -0
- data/test/fixtures/flickr +4 -0
- data/test/fixtures/flickr-download +17 -0
- data/test/fixtures/wordpress +42 -0
- data/test/fixtures/wordpress-categories +18 -0
- data/test/fixtures/wordpress-list +18 -0
- data/test/fixtures/wordpress-post +26 -0
- data/test/git-style-binary/command_test.rb +17 -0
- data/test/git_style_binary_test.rb +21 -0
- data/test/running_binaries_test.rb +224 -0
- data/test/shoulda_macros/matching_stdio.rb +13 -0
- data/test/test_helper.rb +28 -0
- metadata +111 -0
data/lib/ext/core.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Object
|
2
|
+
def returning(value)
|
3
|
+
yield(value)
|
4
|
+
value
|
5
|
+
end unless Object.respond_to?(:returning)
|
6
|
+
end
|
7
|
+
|
8
|
+
class Symbol
|
9
|
+
def to_proc
|
10
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class IO
|
15
|
+
attr_accessor :use_color
|
16
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
# Load the vendor gems
|
5
|
+
$:.unshift(File.dirname(__FILE__) + "/../vendor/gems")
|
6
|
+
%w(trollop).each do |library|
|
7
|
+
begin
|
8
|
+
require "#{library}/lib/#{library}"
|
9
|
+
rescue LoadError
|
10
|
+
begin
|
11
|
+
require 'trollop'
|
12
|
+
rescue LoadError
|
13
|
+
puts "There was an error loading #{library}. Try running git submodule init && git submodule update to correct the problem"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'ext/core'
|
19
|
+
require 'ext/colorize'
|
20
|
+
require 'git-style-binary/autorunner'
|
21
|
+
Dir[File.dirname(__FILE__) + "/git-style-binary/helpers/*.rb"].each {|f| require f}
|
22
|
+
|
23
|
+
module GitStyleBinary
|
24
|
+
|
25
|
+
class << self
|
26
|
+
include Helpers::NameResolver
|
27
|
+
attr_accessor :current_command
|
28
|
+
attr_accessor :primary_command
|
29
|
+
attr_writer :known_commands
|
30
|
+
|
31
|
+
# If set to false GitStyleBinary will not automatically run at exit.
|
32
|
+
attr_writer :run
|
33
|
+
|
34
|
+
# Automatically run at exit?
|
35
|
+
def run?
|
36
|
+
@run ||= false
|
37
|
+
end
|
38
|
+
|
39
|
+
def parser
|
40
|
+
@p ||= Parser.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def known_commands
|
44
|
+
@known_commands ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_primary
|
48
|
+
unless @loaded_primary
|
49
|
+
@loaded_primary = true
|
50
|
+
primary_file = File.join(binary_directory, basename)
|
51
|
+
load primary_file
|
52
|
+
|
53
|
+
if !GitStyleBinary.primary_command # you still dont have a primary load a default
|
54
|
+
GitStyleBinary.primary do
|
55
|
+
run do |command|
|
56
|
+
educate
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_subcommand
|
64
|
+
unless @loaded_subcommand
|
65
|
+
@loaded_subcommand = true
|
66
|
+
cmd_file = GitStyleBinary.binary_filename_for(GitStyleBinary.current_command_name)
|
67
|
+
load cmd_file
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_command_file(name, file)
|
72
|
+
self.name_of_command_being_loaded = name
|
73
|
+
load file
|
74
|
+
self.name_of_command_being_loaded = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# UGLY eek
|
78
|
+
attr_accessor :name_of_command_being_loaded
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
at_exit do
|
84
|
+
unless $! || GitStyleBinary.run?
|
85
|
+
command = GitStyleBinary::AutoRunner.run
|
86
|
+
exit 0
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'git-style-binary/parser'
|
2
|
+
|
3
|
+
module GitStyleBinary
|
4
|
+
class AutoRunner
|
5
|
+
|
6
|
+
def self.run(argv=ARGV)
|
7
|
+
r = new
|
8
|
+
r.run
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
unless GitStyleBinary.run?
|
13
|
+
if !GitStyleBinary.current_command
|
14
|
+
GitStyleBinary.load_primary
|
15
|
+
end
|
16
|
+
GitStyleBinary.current_command.run
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'git-style-binary'
|
2
|
+
|
3
|
+
module GitStyleBinary
|
4
|
+
def self.command(&block)
|
5
|
+
returning Command.new(:constraints => [block]) do |c|
|
6
|
+
c.name ||= (GitStyleBinary.name_of_command_being_loaded || GitStyleBinary.current_command_name)
|
7
|
+
GitStyleBinary.known_commands[c.name] = c
|
8
|
+
|
9
|
+
if !GitStyleBinary.current_command || GitStyleBinary.current_command.is_primary?
|
10
|
+
GitStyleBinary.current_command = c
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.primary(&block)
|
16
|
+
returning Primary.new(:constraints => [block]) do |c|
|
17
|
+
c.name ||= (GitStyleBinary.name_of_command_being_loaded || GitStyleBinary.current_command_name)
|
18
|
+
GitStyleBinary.known_commands[c.name] = c
|
19
|
+
|
20
|
+
GitStyleBinary.primary_command = c unless GitStyleBinary.primary_command
|
21
|
+
GitStyleBinary.current_command = c unless GitStyleBinary.current_command
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Command
|
26
|
+
class << self
|
27
|
+
def defaults
|
28
|
+
lambda do
|
29
|
+
name_desc "#{command.full_name}\#{command.short_desc ? ' - ' + command.short_desc : ''}" # eval jit
|
30
|
+
version_string = defined?(VERSION) ? VERSION : "0.0.1"
|
31
|
+
version "#{version_string} (c) #{Time.now.year}"
|
32
|
+
banner <<-EOS
|
33
|
+
#{"SYNOPSIS".colorize(:red)}
|
34
|
+
#{command.full_name.colorize(:light_blue)} #{all_options_string}
|
35
|
+
|
36
|
+
#{"SUBCOMMANDS".colorize(:red)}
|
37
|
+
\#{GitStyleBinary.pretty_known_subcommands.join("\n ")}
|
38
|
+
|
39
|
+
See '#{command.full_name} help COMMAND' for more information on a specific command.
|
40
|
+
EOS
|
41
|
+
|
42
|
+
opt :verbose, "verbose", :default => false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :constraints
|
48
|
+
attr_reader :opts
|
49
|
+
attr_accessor :name
|
50
|
+
|
51
|
+
def initialize(o={})
|
52
|
+
o.each do |k,v|
|
53
|
+
eval "@#{k.to_s}= v"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def parser
|
58
|
+
@parser ||= begin
|
59
|
+
p = Parser.new
|
60
|
+
p.command = self
|
61
|
+
p
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def constraints
|
66
|
+
@constraints ||= []
|
67
|
+
end
|
68
|
+
|
69
|
+
def run
|
70
|
+
GitStyleBinary.load_primary unless is_primary?
|
71
|
+
GitStyleBinary.load_subcommand if is_primary? && running_subcommand?
|
72
|
+
load_all_parser_constraints
|
73
|
+
@opts = process_args_with_subcmd
|
74
|
+
call_parser_run_block
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def running_subcommand?
|
79
|
+
GitStyleBinary.valid_subcommand?(GitStyleBinary.current_command_name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def load_all_parser_constraints
|
83
|
+
@loaded_all_parser_constraints ||= begin
|
84
|
+
load_parser_default_constraints
|
85
|
+
load_parser_primary_constraints
|
86
|
+
load_parser_local_constraints
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def load_parser_default_constraints
|
92
|
+
parser.consume_all([self.class.defaults])
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_parser_primary_constraints
|
96
|
+
parser.consume_all(GitStyleBinary.primary_command.constraints)
|
97
|
+
end
|
98
|
+
|
99
|
+
def load_parser_local_constraints
|
100
|
+
cur = GitStyleBinary.current_command # see, why isn't 'this' current_command?
|
101
|
+
|
102
|
+
unless self.is_primary? && cur == self
|
103
|
+
# TODO TODO - the key lies in this function. figure out when you hav emore engergy
|
104
|
+
# soo UGLY. see #process_parser! unify with that method
|
105
|
+
# parser.consume_all(constraints) rescue ArgumentError
|
106
|
+
parser.consume_all(cur.constraints)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def call_parser_run_block
|
111
|
+
runs = GitStyleBinary.current_command.parser.runs
|
112
|
+
|
113
|
+
parser.run_callbacks(:before_run, self)
|
114
|
+
parser.runs.last.call(self) # ... not too happy with this
|
115
|
+
parser.run_callbacks(:after_run, self)
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_args_with_subcmd(args = ARGV, *a, &b)
|
119
|
+
cmd = GitStyleBinary.current_command_name
|
120
|
+
vals = process_args(args, *a, &b)
|
121
|
+
parser.leftovers.shift if parser.leftovers[0] == cmd
|
122
|
+
vals
|
123
|
+
end
|
124
|
+
|
125
|
+
# TOOooootally ugly! why? bc load_parser_local_constraints doesn't work
|
126
|
+
# when loading the indivdual commands because it depends on
|
127
|
+
# #current_command. This really sucks and is UGLY.
|
128
|
+
# the todo is to put in 'load_all_parser_constraints' and this works
|
129
|
+
def process_parser!
|
130
|
+
# load_all_parser_constraints
|
131
|
+
|
132
|
+
load_parser_default_constraints
|
133
|
+
load_parser_primary_constraints
|
134
|
+
# load_parser_local_constraints
|
135
|
+
parser.consume_all(constraints)
|
136
|
+
|
137
|
+
# hack
|
138
|
+
parser.consume {
|
139
|
+
opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
|
140
|
+
opt :help, "Show this message" unless @specs[:help] || @long["help"]
|
141
|
+
resolve_default_short_options
|
142
|
+
} # hack
|
143
|
+
end
|
144
|
+
|
145
|
+
def process_args(args = ARGV, *a, &b)
|
146
|
+
p = parser
|
147
|
+
begin
|
148
|
+
vals = p.parse args
|
149
|
+
args.clear
|
150
|
+
p.leftovers.each { |l| args << l }
|
151
|
+
vals # ugly todo
|
152
|
+
rescue Trollop::CommandlineError => e
|
153
|
+
$stderr.puts "Error: #{e.message}."
|
154
|
+
$stderr.puts "Try --help for help."
|
155
|
+
exit(-1)
|
156
|
+
rescue Trollop::HelpNeeded
|
157
|
+
p.educate
|
158
|
+
exit
|
159
|
+
rescue Trollop::VersionNeeded
|
160
|
+
puts p.version
|
161
|
+
exit
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def is_primary?
|
166
|
+
false
|
167
|
+
end
|
168
|
+
|
169
|
+
def argv
|
170
|
+
parser.leftovers
|
171
|
+
end
|
172
|
+
|
173
|
+
def short_desc
|
174
|
+
parser.short_desc
|
175
|
+
end
|
176
|
+
|
177
|
+
def full_name
|
178
|
+
# ugly, should be is_primary?
|
179
|
+
GitStyleBinary.primary_name == name ? GitStyleBinary.primary_name : GitStyleBinary.primary_name + "-" + name
|
180
|
+
end
|
181
|
+
|
182
|
+
def die arg, msg=nil
|
183
|
+
p = parser # create local copy
|
184
|
+
Trollop.instance_eval { @p = p }
|
185
|
+
Trollop::die(arg, msg)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Helper to return the option
|
189
|
+
def [](k)
|
190
|
+
opts[k]
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
class Primary < Command
|
196
|
+
def is_primary?
|
197
|
+
true
|
198
|
+
end
|
199
|
+
def primary
|
200
|
+
self
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GitStyleBinary
|
2
|
+
module Commands
|
3
|
+
class Help
|
4
|
+
# not loving this syntax, but works for now
|
5
|
+
GitStyleBinary.command do
|
6
|
+
short_desc "get help for a specific command"
|
7
|
+
run do |command|
|
8
|
+
|
9
|
+
# this is slightly ugly b/c it has to muck around in the internals to
|
10
|
+
# get information about commands other than itself. This isn't a
|
11
|
+
# typical case
|
12
|
+
def educate_about_command(name)
|
13
|
+
load_all_commands
|
14
|
+
if GitStyleBinary.known_commands.has_key?(name)
|
15
|
+
cmd = GitStyleBinary.known_commands[name]
|
16
|
+
cmd.process_parser!
|
17
|
+
cmd.parser.educate
|
18
|
+
else
|
19
|
+
puts "Unknown command '#{name}'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if command.argv.size > 0
|
24
|
+
command.argv.first == "help" ? educate : educate_about_command(command.argv.first)
|
25
|
+
else
|
26
|
+
educate
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module GitStyleBinary
|
2
|
+
module Helpers
|
3
|
+
module NameResolver
|
4
|
+
|
5
|
+
def basename(filename=zero)
|
6
|
+
File.basename(filename).match(/(.*?)(\-|$)/).captures.first
|
7
|
+
end
|
8
|
+
alias_method :primary_name, :basename
|
9
|
+
|
10
|
+
# checks the bin directory for all files starting with +basename+ and
|
11
|
+
# returns an array of strings specifying the subcommands
|
12
|
+
def subcommand_names(filename=zero)
|
13
|
+
subfiles = Dir[File.join(binary_directory, basename + "-*")]
|
14
|
+
cmds = subfiles.collect{|file| File.basename(file).sub(/^#{basename}-/, '')}.sort
|
15
|
+
cmds += built_in_command_names
|
16
|
+
cmds.uniq
|
17
|
+
end
|
18
|
+
|
19
|
+
def binary_directory(filename=zero)
|
20
|
+
File.dirname(filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
def built_in_commands_directory
|
24
|
+
File.dirname(__FILE__) + "/../commands"
|
25
|
+
end
|
26
|
+
|
27
|
+
def built_in_command_names
|
28
|
+
Dir[built_in_commands_directory + "/*.rb"].collect{|f| File.basename(f.sub(/\.rb$/,''))}
|
29
|
+
end
|
30
|
+
|
31
|
+
def list_subcommands(filename=zero)
|
32
|
+
subcommand_names(filename).join(", ")
|
33
|
+
end
|
34
|
+
|
35
|
+
# load first from users binary directory. then load built-in commands if
|
36
|
+
# available
|
37
|
+
def binary_filename_for(name)
|
38
|
+
user_file = File.join(binary_directory, "#{basename}-#{name}")
|
39
|
+
return user_file if File.exists?(user_file)
|
40
|
+
built_in = File.join(built_in_commands_directory, "#{name}.rb")
|
41
|
+
return built_in if File.exists?(built_in)
|
42
|
+
user_file
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_command_name(filename=zero,argv=ARGV)
|
46
|
+
current = File.basename(zero)
|
47
|
+
first_arg = ARGV[0]
|
48
|
+
return first_arg if valid_subcommand?(first_arg)
|
49
|
+
return basename if basename == current
|
50
|
+
current.sub(/^#{basename}-/, '')
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns the command name with the prefix if needed
|
54
|
+
def full_current_command_name(filename=zero,argv=ARGV)
|
55
|
+
cur = current_command_name(filename, argv)
|
56
|
+
subcmd = cur == basename(filename) ? false : true # is this a subcmd?
|
57
|
+
"%s%s%s" % [basename(filename), subcmd ? "-" : "", subcmd ? current_command_name(filename, argv) : ""]
|
58
|
+
end
|
59
|
+
|
60
|
+
def valid_subcommand?(name)
|
61
|
+
subcommand_names.include?(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def zero
|
65
|
+
$0
|
66
|
+
end
|
67
|
+
|
68
|
+
def pretty_known_subcommands(theme=:long)
|
69
|
+
GitStyleBinary.known_commands.collect do |k,cmd|
|
70
|
+
next if k == basename
|
71
|
+
cmd.process_parser!
|
72
|
+
("%-s%s%-10s" % [basename, '-', k]).colorize(:light_blue) + ("%s " % [theme == :long ? "\n" : ""]) + ("%s" % [cmd.short_desc]) + "\n"
|
73
|
+
end.compact.sort
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module GitStyleBinary
|
2
|
+
module Helpers
|
3
|
+
module Pager
|
4
|
+
|
5
|
+
# by Nathan Weizenbaum - http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
|
6
|
+
def run_pager
|
7
|
+
return if PLATFORM =~ /win32/
|
8
|
+
return unless STDOUT.tty?
|
9
|
+
STDOUT.use_color = true
|
10
|
+
|
11
|
+
read, write = IO.pipe
|
12
|
+
|
13
|
+
unless Kernel.fork # Child process
|
14
|
+
STDOUT.reopen(write)
|
15
|
+
STDERR.reopen(write) if STDERR.tty?
|
16
|
+
read.close
|
17
|
+
write.close
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
# Parent process, become pager
|
22
|
+
STDIN.reopen(read)
|
23
|
+
read.close
|
24
|
+
write.close
|
25
|
+
|
26
|
+
ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
|
27
|
+
|
28
|
+
Kernel.select [STDIN] # Wait until we have input before we start the pager
|
29
|
+
pager = ENV['PAGER'] || 'less -erXF'
|
30
|
+
exec pager rescue exec "/bin/sh", "-c", pager
|
31
|
+
end
|
32
|
+
|
33
|
+
module_function :run_pager
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|