visionmedia-commander 1.2.2 → 2.4.2
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/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
|