visionmedia-commander 1.2.2 → 2.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +85 -0
- data/Manifest +33 -0
- data/README.rdoc +178 -0
- data/Rakefile +8 -30
- data/Todo.rdoc +12 -0
- data/bin/commander +23 -26
- data/commander.gemspec +39 -0
- data/lib/commander/command.rb +227 -31
- data/lib/commander/core_ext/array.rb +24 -0
- data/lib/commander/core_ext/kernel.rb +12 -0
- data/lib/commander/core_ext/object.rb +13 -0
- data/lib/commander/core_ext/string.rb +33 -0
- data/lib/commander/core_ext.rb +5 -0
- data/lib/commander/fileutils.rb +30 -0
- data/lib/commander/help_formatters/base.rb +38 -0
- data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
- data/lib/commander/help_formatters/terminal/help.erb +21 -0
- data/lib/commander/help_formatters/terminal.rb +27 -0
- data/lib/commander/help_formatters.rb +6 -0
- data/lib/commander/import.rb +28 -0
- data/lib/commander/runner.rb +219 -0
- data/lib/commander/user_interaction.rb +202 -0
- data/lib/commander/version.rb +4 -4
- data/lib/commander.rb +46 -6
- data/spec/commander_spec.rb +208 -27
- data/spec/help_formatter_spec.rb +31 -0
- data/spec/spec_helper.rb +25 -0
- data/tasks/docs.rake +13 -0
- data/tasks/gemspec.rake +3 -0
- data/tasks/spec.rake +25 -0
- metadata +65 -25
- data/History.txt +0 -48
- data/Manifest.txt +0 -17
- data/README.txt +0 -76
- data/lib/commander/commander.rb +0 -35
- data/lib/commander/help_generators/default.rb +0 -126
- data/lib/commander/help_generators.rb +0 -2
- data/lib/commander/manager.rb +0 -129
- data/spec/all_spec.rb +0 -6
- data/spec/manager_spec.rb +0 -19
data/lib/commander/command.rb
CHANGED
@@ -1,34 +1,230 @@
|
|
1
1
|
|
2
|
+
require 'optparse'
|
3
|
+
require 'ostruct'
|
4
|
+
|
2
5
|
module Commander
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
6
|
+
class Command
|
7
|
+
|
8
|
+
attr_reader :name, :examples, :options, :proxy_options
|
9
|
+
attr_accessor :syntax, :description, :summary
|
10
|
+
|
11
|
+
##
|
12
|
+
# Initialize new command with specified _name_.
|
13
|
+
|
14
|
+
def initialize name
|
15
|
+
@name, @examples, @when_called = name.to_s, [], {}
|
16
|
+
@options, @proxy_options = [], []
|
17
|
+
end
|
18
|
+
|
19
|
+
#--
|
20
|
+
# Description aliases
|
21
|
+
#++
|
22
|
+
|
23
|
+
alias :long_description= :description=
|
24
|
+
alias :short_description= :summary=
|
25
|
+
|
26
|
+
##
|
27
|
+
# Add a usage example for this command.
|
28
|
+
#
|
29
|
+
# Usage examples are later displayed in help documentation
|
30
|
+
# created by the help formatters.
|
31
|
+
#
|
32
|
+
# === Examples:
|
33
|
+
#
|
34
|
+
# command :something do |c|
|
35
|
+
# c.example "Should do something", "my_command something"
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
|
39
|
+
def example description, command
|
40
|
+
@examples << {
|
41
|
+
:description => description,
|
42
|
+
:command => command,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Add an option.
|
48
|
+
#
|
49
|
+
# Options are parsed via OptionParser so view it
|
50
|
+
# for additional usage documentation. A block may optionally be
|
51
|
+
# passed to handle the option, otherwise the _options_ struct seen below
|
52
|
+
# contains the results of this option. This handles common formats such as:
|
53
|
+
#
|
54
|
+
# -h, --help options.help # => bool
|
55
|
+
# --[with]-feature options.feature # => bool
|
56
|
+
# --large-switch options.large_switch # => bool
|
57
|
+
# --file FILE options.file # => file passed
|
58
|
+
# --list words options.list # => array
|
59
|
+
# --date [DATE] options.date # => date or nil when optional argument not set
|
60
|
+
#
|
61
|
+
# === Examples:
|
62
|
+
#
|
63
|
+
# command :something do |c|
|
64
|
+
# c.option '--recursive', 'Do something recursively'
|
65
|
+
# c.option '--file FILE', 'Specify a file'
|
66
|
+
# c.option('--info', 'Display info') { puts "handle with block" }
|
67
|
+
# c.option '--[no]-feature', 'With or without feature'
|
68
|
+
# c.option '--list files', Array, 'List the files specified'
|
69
|
+
#
|
70
|
+
# c.when_called do |args, options|
|
71
|
+
# do_something_recursively if options.recursive
|
72
|
+
# do_something_with_file options.file if options.file
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# === Help Formatters:
|
77
|
+
#
|
78
|
+
# This method also parses the arguments passed in order to determine
|
79
|
+
# which were switches, and which were descriptions for the
|
80
|
+
# option which can later be used within help formatters
|
81
|
+
# using option[:switches] and option[:description].
|
82
|
+
#
|
83
|
+
# === Input Parsing:
|
84
|
+
#
|
85
|
+
# Since Commander utilizes OptionParser you can pre-parser and evaluate
|
86
|
+
# option arguments. Simply require 'optparse/time', or 'optparse/date', etc.
|
87
|
+
#
|
88
|
+
# c.option '--time TIME', Time
|
89
|
+
# c.option '--date [DATE]', Date
|
90
|
+
#
|
91
|
+
|
92
|
+
def option *args, &block
|
93
|
+
switches, description = seperate_switches_from_description args
|
94
|
+
proc = block_given? ? block : populate_options_to_when_called(switches)
|
95
|
+
@options << {
|
96
|
+
:args => args,
|
97
|
+
:proc => proc,
|
98
|
+
:switches => switches,
|
99
|
+
:description => description,
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Handle execution of command.
|
105
|
+
#
|
106
|
+
# An array of _args_ are passed to the handler, as well as an OpenStruct
|
107
|
+
# containing option values (populated regardless of them being declared).
|
108
|
+
# The handler may be a class, object, or block (see examples below).
|
109
|
+
#
|
110
|
+
# === Examples:
|
111
|
+
#
|
112
|
+
# # Simple block handling
|
113
|
+
# c.when_called do |args, options|
|
114
|
+
# # do something
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# # Create inst of Something and pass args / options
|
118
|
+
# c.when_called MyLib::Command::Something
|
119
|
+
#
|
120
|
+
# # Create inst of Something and use arbitrary method
|
121
|
+
# c.when_called MyLib::Command::Something, :some_method
|
122
|
+
#
|
123
|
+
# # Pass an object to handle callback (requires method symbol)
|
124
|
+
# c.when_called SomeObject, :some_method
|
125
|
+
#
|
126
|
+
|
127
|
+
def when_called *args, &block
|
128
|
+
h = @when_called
|
129
|
+
unless args.empty?
|
130
|
+
case args.first
|
131
|
+
when Class
|
132
|
+
h[:class] = args.shift
|
133
|
+
h[:method] = args.shift
|
134
|
+
else
|
135
|
+
h[:object] = args.shift
|
136
|
+
h[:method] = args.shift
|
137
|
+
end
|
138
|
+
end
|
139
|
+
h[:proc] = block if block_given?
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Run the command with _args_.
|
144
|
+
#
|
145
|
+
# * parses options, call option blocks
|
146
|
+
# * invokes when_called proc
|
147
|
+
#
|
148
|
+
|
149
|
+
def run args = []
|
150
|
+
call parse_options_and_call_procs(args)
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Parses options and calls associated procs
|
155
|
+
# returning the arguments left.
|
156
|
+
|
157
|
+
def parse_options_and_call_procs args = []
|
158
|
+
return args if args.empty?
|
159
|
+
opts = OptionParser.new
|
160
|
+
@options.each { |o| opts.on(*o[:args], &o[:proc]) }
|
161
|
+
opts.parse! args
|
162
|
+
args
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# Call the commands when_called block with _args_.
|
167
|
+
|
168
|
+
def call args = []
|
169
|
+
h = @when_called
|
170
|
+
case
|
171
|
+
when h[:class]
|
172
|
+
if h[:method].nil?
|
173
|
+
h[:class].new args, proxy_option_struct
|
174
|
+
else
|
175
|
+
h[:class].new.send h[:method], args, proxy_option_struct
|
176
|
+
end
|
177
|
+
when h[:object] : h[:object].send(h[:method], args, proxy_option_struct)
|
178
|
+
when h[:proc] : h[:proc].call args, proxy_option_struct
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Attempts to generate a method name symbol from _switch_.
|
184
|
+
# For example:
|
185
|
+
#
|
186
|
+
# -h # => :h
|
187
|
+
# --trace # => :trace
|
188
|
+
# --some-switch # => :some_switch
|
189
|
+
# --[with]-feature # => :feature
|
190
|
+
# --file FILE # => :file
|
191
|
+
# --list of, things # => :list
|
192
|
+
|
193
|
+
def sym_from_switch switch
|
194
|
+
switch.gsub(/\[.*\]/, '').scan(/-([a-z]+)/).join('_').to_sym rescue nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def inspect #:nodoc:
|
198
|
+
"#<Command:#{@name}>"
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def proxy_option_struct #:nodoc:
|
204
|
+
options = OpenStruct.new
|
205
|
+
@proxy_options.each { |o| options.send("#{o[:method]}=", o[:value]) }
|
206
|
+
options
|
207
|
+
end
|
208
|
+
|
209
|
+
def seperate_switches_from_description args #:nodoc:
|
210
|
+
# TODO: refactor this goodness
|
211
|
+
switches = args.find_all { |a| a.index('-') == 0 if a.is_a? String }
|
212
|
+
description = args.last unless !args.last.is_a? String or args.last.index('-') == 0
|
213
|
+
[switches, description]
|
214
|
+
end
|
215
|
+
|
216
|
+
##
|
217
|
+
# Pass option values to the when_called proc when a block
|
218
|
+
# is not specifically supplied to #option.
|
219
|
+
|
220
|
+
def populate_options_to_when_called switches #:nodoc:
|
221
|
+
Proc.new do |args|
|
222
|
+
@proxy_options << {
|
223
|
+
:method => sym_from_switch(switches.last),
|
224
|
+
:value => args,
|
225
|
+
}
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
34
230
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
class Array
|
3
|
+
|
4
|
+
##
|
5
|
+
# Split +string+ into an array.
|
6
|
+
#
|
7
|
+
# === Highline example:
|
8
|
+
#
|
9
|
+
# # ask invokes Array#parse
|
10
|
+
# list = ask 'Favorite cookies:', Array
|
11
|
+
#
|
12
|
+
|
13
|
+
def self.parse string
|
14
|
+
string.split
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Delete switches such as -h or --help.
|
19
|
+
|
20
|
+
def delete_switches
|
21
|
+
self.dup.delete_if { |v| v.to_s =~ /^-/ }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
class String
|
3
|
+
|
4
|
+
##
|
5
|
+
# Replace _hash_ keys with associated values. Mutative.
|
6
|
+
|
7
|
+
def tokenize! hash
|
8
|
+
hash.each_pair do |k, v|
|
9
|
+
self.gsub! Regexp.new(":#{k}"), v.to_s
|
10
|
+
end
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Replace _hash_ keys with associated values.
|
16
|
+
|
17
|
+
def tokenize hash
|
18
|
+
self.dup.tokenize! hash
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Converts a string to camelcase.
|
23
|
+
|
24
|
+
def camelcase upcase_first_letter = true
|
25
|
+
up = upcase_first_letter
|
26
|
+
str = dup
|
27
|
+
str.gsub!(/\/(.?)/){ "::#{$1.upcase}" }
|
28
|
+
str.gsub!(/(?:_+|-+)([a-z])/){ $1.upcase }
|
29
|
+
str.gsub!(/(\A|\s)([a-z])/){ $1 + $2.upcase } if up
|
30
|
+
str
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module VerboseFileUtils
|
5
|
+
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
##
|
9
|
+
# Wrap _methods_ with _action_ log message.
|
10
|
+
|
11
|
+
def self.log action, *methods
|
12
|
+
methods.each do |meth|
|
13
|
+
define_method meth do |*args|
|
14
|
+
Commander::UI.log "#{action}", *args
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
log "remove", :rm, :rm_r, :rm_rf, :rmdir
|
21
|
+
log "create", :touch, :mkdir, :mkdir_p
|
22
|
+
log "copy", :cp, :cp_r
|
23
|
+
log "move", :mv
|
24
|
+
log "change", :cd
|
25
|
+
log "link", :ln, :ln_s
|
26
|
+
log "install", :install
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
include VerboseFileUtils
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module Commander
|
3
|
+
|
4
|
+
##
|
5
|
+
# = Help Formatter
|
6
|
+
#
|
7
|
+
# Commander's help formatters control the output when
|
8
|
+
# either the help command, or --help switch are called.
|
9
|
+
# The default formatter is Commander::HelpFormatter::Terminal.
|
10
|
+
|
11
|
+
module HelpFormatter
|
12
|
+
|
13
|
+
class Base
|
14
|
+
|
15
|
+
##
|
16
|
+
# Access the current command runner via +@runner+.
|
17
|
+
|
18
|
+
def initialize runner
|
19
|
+
@runner = runner
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Renders global help.
|
24
|
+
|
25
|
+
def render
|
26
|
+
"Implement global help here"
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Renders +command+ specific help.
|
31
|
+
|
32
|
+
def render_command command
|
33
|
+
"Implement help for #{command.name} here"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
<%= color "NAME", :bold %>:
|
3
|
+
|
4
|
+
<%= @name %>
|
5
|
+
|
6
|
+
<%= color "DESCRIPTION", :bold %>:
|
7
|
+
|
8
|
+
<%= @description || @summary || 'No description.' -%>
|
9
|
+
|
10
|
+
<% if @syntax -%>
|
11
|
+
|
12
|
+
<%= color "SYNOPSIS", :bold %>:
|
13
|
+
|
14
|
+
<%= @syntax -%>
|
15
|
+
|
16
|
+
<% end -%>
|
17
|
+
<% unless @examples.empty? -%>
|
18
|
+
|
19
|
+
<%= color "EXAMPLES", :bold %>:
|
20
|
+
<% @examples.each do |example| -%>
|
21
|
+
|
22
|
+
# <%= example[:description] %>
|
23
|
+
<%= example[:command] %>
|
24
|
+
<% end -%>
|
25
|
+
<% end -%>
|
26
|
+
<% unless @options.empty? -%>
|
27
|
+
|
28
|
+
<%= color "OPTIONS", :bold %>:
|
29
|
+
<% @options.each do |option| -%>
|
30
|
+
|
31
|
+
<%= option[:switches].join ', ' %>
|
32
|
+
<%= option[:description] %>
|
33
|
+
<% end -%>
|
34
|
+
<% end -%>
|
35
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
<%= color "NAME", :bold %>:
|
3
|
+
|
4
|
+
<%= program :name %>
|
5
|
+
|
6
|
+
<%= color "DESCRIPTION", :bold %>:
|
7
|
+
|
8
|
+
<%= program :description %>
|
9
|
+
|
10
|
+
<%= color "SUB-COMMANDS", :bold %>:
|
11
|
+
<% @commands.each_pair do |name, command| %>
|
12
|
+
<%= "%-14s %s" % [command.name, command.summary || command.description] -%>
|
13
|
+
<% end %>
|
14
|
+
<% if program :help -%>
|
15
|
+
<% program(:help).each_pair do |title, body| %>
|
16
|
+
<%= color title.to_s.upcase, :bold %>:
|
17
|
+
|
18
|
+
<%= body %>
|
19
|
+
<% end %>
|
20
|
+
<% end -%>
|
21
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Commander
|
5
|
+
module HelpFormatter
|
6
|
+
|
7
|
+
##
|
8
|
+
# = Terminal
|
9
|
+
#
|
10
|
+
# Outputs help in a terminal friendly format,
|
11
|
+
# utilizing asni formatting via the highline gem.
|
12
|
+
|
13
|
+
class Terminal < Base
|
14
|
+
def render
|
15
|
+
template(:help).result @runner.get_binding
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_command command
|
19
|
+
template(:command_help).result command.get_binding
|
20
|
+
end
|
21
|
+
|
22
|
+
def template name
|
23
|
+
ERB.new(File.read(File.expand_path(File.dirname(__FILE__)) + "/terminal/#{name}.erb"), nil, "-")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
##
|
5
|
+
# Makes the following Commander methods globally available:
|
6
|
+
#
|
7
|
+
# * Commander::Runner#add_command
|
8
|
+
# * Commander::Runner#get_command
|
9
|
+
# * Commander::Runner#command
|
10
|
+
# * Commander::Runner#commands
|
11
|
+
# * Commander::Runner#program
|
12
|
+
# * Commander::UI::ProgressBar#progress
|
13
|
+
|
14
|
+
module Kernel
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :$command_runner, :add_command, :get_command, :command, :program, :run!, :commands
|
17
|
+
def_delegators Commander::UI::ProgressBar, :progress
|
18
|
+
|
19
|
+
def command_runner #:nodoc:
|
20
|
+
$command_runner
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing meth, *args, &block
|
24
|
+
if meth.to_s =~ /^ask_for_([\w]+)/
|
25
|
+
$terminal.ask args.first, eval($1.camelcase)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|