taco_it 1.5.1 → 1.5.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/bin/taco +1 -1
- data/lib/taco/commander/blank.rb +8 -0
- data/lib/taco/commander/command.rb +226 -0
- data/lib/taco/commander/core_ext/array.rb +26 -0
- data/lib/taco/commander/core_ext/object.rb +11 -0
- data/lib/taco/commander/core_ext.rb +3 -0
- data/lib/taco/commander/delegates.rb +13 -0
- data/lib/taco/commander/help_formatters/base.rb +18 -0
- data/lib/taco/commander/help_formatters/terminal/command_help.erb +35 -0
- data/lib/taco/commander/help_formatters/terminal/help.erb +36 -0
- data/lib/taco/commander/help_formatters/terminal.rb +20 -0
- data/lib/taco/commander/help_formatters/terminal_compact/command_help.erb +27 -0
- data/lib/taco/commander/help_formatters/terminal_compact/help.erb +29 -0
- data/lib/taco/commander/help_formatters/terminal_compact.rb +12 -0
- data/lib/taco/commander/help_formatters.rb +13 -0
- data/lib/taco/commander/import.rb +8 -0
- data/lib/taco/commander/platform.rb +8 -0
- data/lib/taco/commander/runner.rb +405 -0
- data/lib/taco/commander/version.rb +3 -0
- metadata +23 -5
data/bin/taco
CHANGED
@@ -0,0 +1,226 @@
|
|
1
|
+
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Commander
|
5
|
+
class Command
|
6
|
+
|
7
|
+
attr_accessor :name, :examples, :syntax, :description
|
8
|
+
attr_accessor :summary, :proxy_options, :options
|
9
|
+
|
10
|
+
##
|
11
|
+
# Options struct.
|
12
|
+
|
13
|
+
class Options
|
14
|
+
include Blank
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@table = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def __hash__
|
21
|
+
@table
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing meth, *args, &block
|
25
|
+
meth.to_s =~ /=$/ ? @table[meth.to_s.chop.to_sym] = args.first : @table[meth]
|
26
|
+
end
|
27
|
+
|
28
|
+
def default defaults = {}
|
29
|
+
@table = defaults.merge! @table
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"<Commander::Command::Options #{ __hash__.map { |k,v| "#{k}=#{v.inspect}" }.join(', ') }>"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Initialize new command with specified _name_.
|
39
|
+
|
40
|
+
def initialize name
|
41
|
+
@name, @examples, @when_called = name.to_s, [], []
|
42
|
+
@options, @proxy_options = [], []
|
43
|
+
@argument_validator = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Add a usage example for this command.
|
48
|
+
#
|
49
|
+
# Usage examples are later displayed in help documentation
|
50
|
+
# created by the help formatters.
|
51
|
+
#
|
52
|
+
# === Examples
|
53
|
+
#
|
54
|
+
# command :something do |c|
|
55
|
+
# c.example "Should do something", "my_command something"
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
|
59
|
+
def example description, command
|
60
|
+
@examples << [description, command]
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def arguments proc
|
65
|
+
@argument_validator = proc
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Add an option.
|
70
|
+
#
|
71
|
+
# Options are parsed via OptionParser so view it
|
72
|
+
# for additional usage documentation. A block may optionally be
|
73
|
+
# passed to handle the option, otherwise the _options_ struct seen below
|
74
|
+
# contains the results of this option. This handles common formats such as:
|
75
|
+
#
|
76
|
+
# -h, --help options.help # => bool
|
77
|
+
# --[no-]feature options.feature # => bool
|
78
|
+
# --large-switch options.large_switch # => bool
|
79
|
+
# --file FILE options.file # => file passed
|
80
|
+
# --list WORDS options.list # => array
|
81
|
+
# --date [DATE] options.date # => date or nil when optional argument not set
|
82
|
+
#
|
83
|
+
# === Examples
|
84
|
+
#
|
85
|
+
# command :something do |c|
|
86
|
+
# c.option '--recursive', 'Do something recursively'
|
87
|
+
# c.option '--file FILE', 'Specify a file'
|
88
|
+
# c.option('--info', 'Display info') { puts "handle with block" }
|
89
|
+
# c.option '--[no-]feature', 'With or without feature'
|
90
|
+
# c.option '--list FILES', Array, 'List the files specified'
|
91
|
+
#
|
92
|
+
# c.when_called do |args, options|
|
93
|
+
# do_something_recursively if options.recursive
|
94
|
+
# do_something_with_file options.file if options.file
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# === Help Formatters
|
99
|
+
#
|
100
|
+
# This method also parses the arguments passed in order to determine
|
101
|
+
# which were switches, and which were descriptions for the
|
102
|
+
# option which can later be used within help formatters
|
103
|
+
# using option[:switches] and option[:description].
|
104
|
+
#
|
105
|
+
# === Input Parsing
|
106
|
+
#
|
107
|
+
# Since Commander utilizes OptionParser you can pre-parse and evaluate
|
108
|
+
# option arguments. Simply require 'optparse/time', or 'optparse/date', as these
|
109
|
+
# objects must respond to #parse.
|
110
|
+
#
|
111
|
+
# c.option '--time TIME', Time
|
112
|
+
# c.option '--date [DATE]', Date
|
113
|
+
#
|
114
|
+
|
115
|
+
def option *args, &block
|
116
|
+
switches, description = Runner.separate_switches_from_description(*args)
|
117
|
+
proc = block || option_proc(switches)
|
118
|
+
@options << {
|
119
|
+
:args => args,
|
120
|
+
:proc => proc,
|
121
|
+
:switches => switches,
|
122
|
+
:description => description,
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Handle execution of command. The handler may be a class,
|
128
|
+
# object, or block (see examples below).
|
129
|
+
#
|
130
|
+
# === Examples
|
131
|
+
#
|
132
|
+
# # Simple block handling
|
133
|
+
# c.when_called do |args, options|
|
134
|
+
# # do something
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# # Create inst of Something and pass args / options
|
138
|
+
# c.when_called MyLib::Command::Something
|
139
|
+
#
|
140
|
+
# # Create inst of Something and use arbitrary method
|
141
|
+
# c.when_called MyLib::Command::Something, :some_method
|
142
|
+
#
|
143
|
+
# # Pass an object to handle callback (requires method symbol)
|
144
|
+
# c.when_called SomeObject, :some_method
|
145
|
+
#
|
146
|
+
|
147
|
+
def when_called *args, &block
|
148
|
+
raise ArgumentError, 'must pass an object, class, or block.' if args.empty? and !block
|
149
|
+
@when_called = block ? [block] : args
|
150
|
+
end
|
151
|
+
alias :action :when_called
|
152
|
+
|
153
|
+
##
|
154
|
+
# Run the command with _args_.
|
155
|
+
#
|
156
|
+
# * parses options, call option blocks
|
157
|
+
# * invokes when_called proc
|
158
|
+
#
|
159
|
+
|
160
|
+
def run *args
|
161
|
+
call parse_options_and_call_procs(*args)
|
162
|
+
end
|
163
|
+
|
164
|
+
#:stopdoc:
|
165
|
+
|
166
|
+
##
|
167
|
+
# Parses options and calls associated procs,
|
168
|
+
# returning the arguments remaining.
|
169
|
+
|
170
|
+
def parse_options_and_call_procs *args
|
171
|
+
return args if args.empty?
|
172
|
+
@options.inject OptionParser.new do |opts, option|
|
173
|
+
opts.on(*option[:args], &option[:proc])
|
174
|
+
opts
|
175
|
+
end.parse! args
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Call the commands when_called block with _args_.
|
180
|
+
|
181
|
+
def call args = []
|
182
|
+
object = @when_called.shift
|
183
|
+
meth = @when_called.shift || :call
|
184
|
+
options = proxy_option_struct
|
185
|
+
|
186
|
+
if @argument_validator
|
187
|
+
raise ArgumentError.new("Unexpected arguments: #{args.join(' ')}") unless @argument_validator.call(args)
|
188
|
+
elsif args.size > 0
|
189
|
+
raise ArgumentError.new("Unexpected arguments: #{args.join(' ')}")
|
190
|
+
end
|
191
|
+
|
192
|
+
case object
|
193
|
+
when Proc ; object.call(args, options)
|
194
|
+
when Class ; meth != :call ? object.new.send(meth, args, options) : object.new(args, options)
|
195
|
+
else object.send(meth, args, options) if object
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# Creates an Options instance populated with the option values
|
201
|
+
# collected by the #option_proc.
|
202
|
+
|
203
|
+
def proxy_option_struct
|
204
|
+
proxy_options.inject Options.new do |options, (option, value)|
|
205
|
+
# options that are present will evaluate to true
|
206
|
+
value = true if value.nil?
|
207
|
+
options.__send__ :"#{option}=", value
|
208
|
+
options
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Option proxy proc used when a block is not explicitly passed
|
214
|
+
# via the #option method. This allows commander to auto-populate
|
215
|
+
# and work with option values.
|
216
|
+
|
217
|
+
def option_proc switches
|
218
|
+
lambda { |value| proxy_options << [Runner.switch_to_sym(switches.last), value] }
|
219
|
+
end
|
220
|
+
|
221
|
+
def inspect
|
222
|
+
"<Commander::Command:#{name}>"
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class Array
|
3
|
+
|
4
|
+
##
|
5
|
+
# Split _string_ into an array. Used in
|
6
|
+
# conjunction with Highline's #ask, or #ask_for_array
|
7
|
+
# methods, which must respond to #parse.
|
8
|
+
#
|
9
|
+
# This method allows escaping of whitespace. For example
|
10
|
+
# the arguments foo bar\ baz will become ['foo', 'bar baz']
|
11
|
+
#
|
12
|
+
# === Example
|
13
|
+
#
|
14
|
+
# # ask invokes Array#parse
|
15
|
+
# list = ask 'Favorite cookies:', Array
|
16
|
+
#
|
17
|
+
# # or use ask_for_CLASS
|
18
|
+
# list = ask_for_array 'Favorite cookies: '
|
19
|
+
#
|
20
|
+
|
21
|
+
def self.parse string
|
22
|
+
# Using reverse + lookahead to work around Ruby 1.8's lack of lookbehind
|
23
|
+
string.reverse.split(/\s(?!\\)/).reverse.map { |s| s.reverse.gsub('\\ ', ' ') }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Commander
|
3
|
+
module Delegates
|
4
|
+
%w( add_command command program run! global_option
|
5
|
+
commands alias_command default_command ).each do |meth|
|
6
|
+
eval <<-END, binding, __FILE__, __LINE__
|
7
|
+
def #{meth} *args, &block
|
8
|
+
::Commander::Runner.instance.#{meth} *args, &block
|
9
|
+
end
|
10
|
+
END
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
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
|
+
class Base
|
13
|
+
def initialize runner; @runner = runner end
|
14
|
+
def render; 'Implement global help here' end
|
15
|
+
def render_command command; "Implement help for #{command.name} here" end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
<%= $terminal.color "NAME", :bold %>:
|
3
|
+
|
4
|
+
<%= @name %>
|
5
|
+
<% if @syntax -%>
|
6
|
+
|
7
|
+
<%= $terminal.color "SYNOPSIS", :bold %>:
|
8
|
+
|
9
|
+
<%= @syntax -%>
|
10
|
+
|
11
|
+
<% end -%>
|
12
|
+
|
13
|
+
<%= $terminal.color "DESCRIPTION", :bold %>:
|
14
|
+
|
15
|
+
<%= Commander::HelpFormatter.indent 4, (@description || @summary || 'No description.') -%>
|
16
|
+
|
17
|
+
<% unless @examples.empty? -%>
|
18
|
+
|
19
|
+
<%= $terminal.color "EXAMPLES", :bold %>:
|
20
|
+
<% for description, command in @examples -%>
|
21
|
+
|
22
|
+
# <%= description %>
|
23
|
+
<%= command %>
|
24
|
+
<% end -%>
|
25
|
+
<% end -%>
|
26
|
+
<% unless @options.empty? -%>
|
27
|
+
|
28
|
+
<%= $terminal.color "OPTIONS", :bold %>:
|
29
|
+
<% for option in @options -%>
|
30
|
+
|
31
|
+
<%= option[:switches].join ', ' %>
|
32
|
+
<%= option[:description] %>
|
33
|
+
<% end -%>
|
34
|
+
<% end -%>
|
35
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<%= $terminal.color "NAME", :bold %>:
|
2
|
+
|
3
|
+
<%= program :name %>
|
4
|
+
|
5
|
+
<%= $terminal.color "DESCRIPTION", :bold %>:
|
6
|
+
|
7
|
+
<%= Commander::HelpFormatter.indent 4, program(:description) %>
|
8
|
+
|
9
|
+
<%= $terminal.color "COMMANDS", :bold %>:
|
10
|
+
<% for name, command in @commands.sort -%>
|
11
|
+
<% unless alias? name %>
|
12
|
+
<%= "%-20s %s" % [command.name, command.summary || command.description] -%>
|
13
|
+
<% end -%>
|
14
|
+
<% end %>
|
15
|
+
<% unless @aliases.empty? %>
|
16
|
+
<%= $terminal.color "ALIASES", :bold %>:
|
17
|
+
<% for alias_name, args in @aliases.sort %>
|
18
|
+
<%= "%-20s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] -%>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
<% unless @options.empty? -%>
|
22
|
+
<%= $terminal.color "GLOBAL OPTIONS", :bold %>:
|
23
|
+
<% for option in @options -%>
|
24
|
+
|
25
|
+
<%= option[:switches].join ', ' %>
|
26
|
+
<%= option[:description] %>
|
27
|
+
<% end -%>
|
28
|
+
<% end -%>
|
29
|
+
<% if program :help -%>
|
30
|
+
<% for title, body in program(:help) %>
|
31
|
+
<%= $terminal.color title.to_s.upcase, :bold %>:
|
32
|
+
|
33
|
+
<%= body %>
|
34
|
+
<% end -%>
|
35
|
+
<% end -%>
|
36
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Commander
|
5
|
+
module HelpFormatter
|
6
|
+
class Terminal < Base
|
7
|
+
def render
|
8
|
+
template(:help).result @runner.get_binding
|
9
|
+
end
|
10
|
+
|
11
|
+
def render_command command
|
12
|
+
template(:command_help).result command.get_binding
|
13
|
+
end
|
14
|
+
|
15
|
+
def template name
|
16
|
+
ERB.new(File.read(File.join(File.dirname(__FILE__), 'terminal', "#{name}.erb")), nil, '-')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
<%= @name %>
|
3
|
+
<% if @syntax -%>
|
4
|
+
|
5
|
+
Usage: <%= @syntax %>
|
6
|
+
<% end -%>
|
7
|
+
<% if @description || @summary -%>
|
8
|
+
|
9
|
+
<%= @description || @summary %>
|
10
|
+
<% end -%>
|
11
|
+
<% unless @examples.empty? -%>
|
12
|
+
|
13
|
+
Examples:
|
14
|
+
<% for description, command in @examples -%>
|
15
|
+
|
16
|
+
# <%= description %>
|
17
|
+
<%= command %>
|
18
|
+
<% end -%>
|
19
|
+
<% end -%>
|
20
|
+
<% unless @options.empty? -%>
|
21
|
+
|
22
|
+
Options:
|
23
|
+
<% for option in @options -%>
|
24
|
+
<%= "%-20s %s" % [option[:switches].join(', '), option[:description]] %>
|
25
|
+
<% end -%>
|
26
|
+
<% end -%>
|
27
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<%= program :name %>
|
2
|
+
|
3
|
+
<%= program :description %>
|
4
|
+
|
5
|
+
Commands:
|
6
|
+
<% for name, command in @commands.sort -%>
|
7
|
+
<% unless alias? name -%>
|
8
|
+
<%= "%-20s %s" % [command.name, command.summary || command.description] %>
|
9
|
+
<% end -%>
|
10
|
+
<% end -%>
|
11
|
+
<% unless @aliases.empty? %>
|
12
|
+
Aliases:
|
13
|
+
<% for alias_name, args in @aliases.sort -%>
|
14
|
+
<%= "%-20s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] %>
|
15
|
+
<% end -%>
|
16
|
+
<% end %>
|
17
|
+
<% unless @options.empty? -%>
|
18
|
+
Global Options:
|
19
|
+
<% for option in @options -%>
|
20
|
+
<%= "%-20s %s" % [option[:switches].join(', '), option[:description]] -%>
|
21
|
+
<% end -%>
|
22
|
+
<% end -%>
|
23
|
+
<% if program :help -%>
|
24
|
+
<% for title, body in program(:help) %>
|
25
|
+
<%= title %>:
|
26
|
+
<%= body %>
|
27
|
+
<% end %>
|
28
|
+
<% end -%>
|
29
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Commander
|
3
|
+
module HelpFormatter
|
4
|
+
autoload :Base, 'taco/commander/help_formatters/base'
|
5
|
+
autoload :Terminal, 'taco/commander/help_formatters/terminal'
|
6
|
+
autoload :TerminalCompact, 'taco/commander/help_formatters/terminal_compact'
|
7
|
+
|
8
|
+
module_function
|
9
|
+
def indent amount, text
|
10
|
+
text.gsub("\n", "\n" + (' ' * amount))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,405 @@
|
|
1
|
+
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Commander
|
5
|
+
class Runner
|
6
|
+
|
7
|
+
#--
|
8
|
+
# Exceptions
|
9
|
+
#++
|
10
|
+
|
11
|
+
class CommandError < StandardError; end
|
12
|
+
class InvalidCommandError < CommandError; end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Array of commands.
|
16
|
+
|
17
|
+
attr_reader :commands
|
18
|
+
|
19
|
+
##
|
20
|
+
# Global options.
|
21
|
+
|
22
|
+
attr_reader :options
|
23
|
+
|
24
|
+
##
|
25
|
+
# Hash of help formatter aliases.
|
26
|
+
|
27
|
+
attr_reader :help_formatter_aliases
|
28
|
+
|
29
|
+
##
|
30
|
+
# Initialize a new command runner. Optionally
|
31
|
+
# supplying _args_ for mocking, or arbitrary usage.
|
32
|
+
|
33
|
+
def initialize args = ARGV
|
34
|
+
@args, @commands, @aliases, @options = args, {}, {}, []
|
35
|
+
@help_formatter_aliases = help_formatter_alias_defaults
|
36
|
+
@program = program_defaults
|
37
|
+
create_default_commands
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Return singleton Runner instance.
|
42
|
+
|
43
|
+
def self.instance
|
44
|
+
@singleton ||= new
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Run command parsing and execution process.
|
49
|
+
|
50
|
+
def run!
|
51
|
+
trace = false
|
52
|
+
require_program :version, :description
|
53
|
+
trap('INT') { abort program(:int_message) } if program(:int_message)
|
54
|
+
trap('INT') { program(:int_block).call } if program(:int_block)
|
55
|
+
global_option('-h', '--help', 'Display help documentation') do
|
56
|
+
args = @args - %w[-h --help]
|
57
|
+
command(:help).run(*args)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
global_option('-v', '--version', 'Display version information') { say version; return }
|
61
|
+
global_option('-t', '--trace', 'Display backtrace when an error occurs') { trace = true }
|
62
|
+
parse_global_options
|
63
|
+
remove_global_options options, @args
|
64
|
+
unless trace
|
65
|
+
begin
|
66
|
+
run_active_command
|
67
|
+
rescue InvalidCommandError => e
|
68
|
+
abort "Error: Invalid command. See 'taco --help' for more information."
|
69
|
+
rescue Exception => e
|
70
|
+
abort "Error: #{e.to_s}"
|
71
|
+
end
|
72
|
+
else
|
73
|
+
run_active_command
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Return program version.
|
79
|
+
|
80
|
+
def version
|
81
|
+
'%s %s' % [program(:name), program(:version)]
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Assign program information.
|
86
|
+
#
|
87
|
+
# === Examples
|
88
|
+
#
|
89
|
+
# # Set data
|
90
|
+
# program :name, 'Commander'
|
91
|
+
# program :version, Commander::VERSION
|
92
|
+
# program :description, 'Commander utility program.'
|
93
|
+
# program :help, 'Copyright', '2008 TJ Holowaychuk'
|
94
|
+
# program :help, 'Anything', 'You want'
|
95
|
+
# program :int_message 'Bye bye!'
|
96
|
+
# program :help_formatter, :compact
|
97
|
+
# program :help_formatter, Commander::HelpFormatter::TerminalCompact
|
98
|
+
#
|
99
|
+
# # Get data
|
100
|
+
# program :name # => 'Commander'
|
101
|
+
#
|
102
|
+
# === Keys
|
103
|
+
#
|
104
|
+
# :version (required) Program version triple, ex: '0.0.1'
|
105
|
+
# :description (required) Program description
|
106
|
+
# :name Program name, defaults to basename of executable
|
107
|
+
# :help_formatter Defaults to Commander::HelpFormatter::Terminal
|
108
|
+
# :help Allows addition of arbitrary global help blocks
|
109
|
+
# :int_message Message to display when interrupted (CTRL + C)
|
110
|
+
#
|
111
|
+
|
112
|
+
def program key, *args, &block
|
113
|
+
if key == :help and !args.empty?
|
114
|
+
@program[:help] ||= {}
|
115
|
+
@program[:help][args.first] = args.at(1)
|
116
|
+
elsif key == :help_formatter && !args.empty?
|
117
|
+
@program[key] = (@help_formatter_aliases[args.first] || args.first)
|
118
|
+
elsif block
|
119
|
+
@program[key] = block
|
120
|
+
else
|
121
|
+
unless args.empty?
|
122
|
+
@program[key] = (args.count == 1 && args[0]) || args
|
123
|
+
end
|
124
|
+
@program[key]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Creates and yields a command instance when a block is passed.
|
130
|
+
# Otherwise attempts to return the command, raising InvalidCommandError when
|
131
|
+
# it does not exist.
|
132
|
+
#
|
133
|
+
# === Examples
|
134
|
+
#
|
135
|
+
# command :my_command do |c|
|
136
|
+
# c.when_called do |args|
|
137
|
+
# # Code
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
|
142
|
+
def command name, &block
|
143
|
+
yield add_command(Commander::Command.new(name)) if block
|
144
|
+
@commands[name.to_s]
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Add a global option; follows the same syntax as Command#option
|
149
|
+
# This would be used for switches such as --version, --trace, etc.
|
150
|
+
|
151
|
+
def global_option *args, &block
|
152
|
+
switches, description = Runner.separate_switches_from_description *args
|
153
|
+
@options << {
|
154
|
+
:args => args,
|
155
|
+
:proc => block,
|
156
|
+
:switches => switches,
|
157
|
+
:description => description,
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Alias command _name_ with _alias_name_. Optionally _args_ may be passed
|
163
|
+
# as if they were being passed straight to the original command via the command-line.
|
164
|
+
|
165
|
+
def alias_command alias_name, name, *args
|
166
|
+
@commands[alias_name.to_s] = command name
|
167
|
+
@aliases[alias_name.to_s] = args
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Default command _name_ to be used when no other
|
172
|
+
# command is found in the arguments.
|
173
|
+
|
174
|
+
def default_command name
|
175
|
+
@default_command = name
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Add a command object to this runner.
|
180
|
+
|
181
|
+
def add_command command
|
182
|
+
@commands[command.name] = command
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Check if command _name_ is an alias.
|
187
|
+
|
188
|
+
def alias? name
|
189
|
+
@aliases.include? name.to_s
|
190
|
+
end
|
191
|
+
|
192
|
+
##
|
193
|
+
# Check if a command _name_ exists.
|
194
|
+
|
195
|
+
def command_exists? name
|
196
|
+
@commands[name.to_s]
|
197
|
+
end
|
198
|
+
|
199
|
+
#:stopdoc:
|
200
|
+
|
201
|
+
##
|
202
|
+
# Get active command within arguments passed to this runner.
|
203
|
+
|
204
|
+
def active_command
|
205
|
+
@__active_command ||= command(command_name_from_args)
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# Attempts to locate a command name from within the arguments.
|
210
|
+
# Supports multi-word commands, using the largest possible match.
|
211
|
+
|
212
|
+
def command_name_from_args
|
213
|
+
@__command_name_from_args ||= (valid_command_names_from(*@args.dup).sort.last || @default_command)
|
214
|
+
end
|
215
|
+
|
216
|
+
##
|
217
|
+
# Returns array of valid command names found within _args_.
|
218
|
+
|
219
|
+
def valid_command_names_from *args
|
220
|
+
arg_string = args.delete_if { |value| value =~ /^-/ }.join ' '
|
221
|
+
commands.keys.find_all { |name| name if /^#{name}\b/.match arg_string }
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Help formatter instance.
|
226
|
+
|
227
|
+
def help_formatter
|
228
|
+
@__help_formatter ||= program(:help_formatter).new self
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Return arguments without the command name.
|
233
|
+
|
234
|
+
def args_without_command_name
|
235
|
+
removed = []
|
236
|
+
parts = command_name_from_args.split rescue []
|
237
|
+
@args.dup.delete_if do |arg|
|
238
|
+
removed << arg if parts.include?(arg) and not removed.include?(arg)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# Returns hash of help formatter alias defaults.
|
244
|
+
|
245
|
+
def help_formatter_alias_defaults
|
246
|
+
return :compact => HelpFormatter::TerminalCompact
|
247
|
+
end
|
248
|
+
|
249
|
+
##
|
250
|
+
# Returns hash of program defaults.
|
251
|
+
|
252
|
+
def program_defaults
|
253
|
+
return :help_formatter => HelpFormatter::Terminal,
|
254
|
+
:name => File.basename($0)
|
255
|
+
end
|
256
|
+
|
257
|
+
##
|
258
|
+
# Creates default commands such as 'help' which is
|
259
|
+
# essentially the same as using the --help switch.
|
260
|
+
|
261
|
+
def create_default_commands
|
262
|
+
command :help do |c|
|
263
|
+
c.syntax = 'commander help [command]'
|
264
|
+
c.description = 'Display global or [command] help documentation.'
|
265
|
+
c.example 'Display global help', 'command help'
|
266
|
+
c.example "Display help for 'foo'", 'command help foo'
|
267
|
+
c.when_called do |args, options|
|
268
|
+
if args.empty?
|
269
|
+
say help_formatter.render
|
270
|
+
else
|
271
|
+
command = command args.join(' ')
|
272
|
+
begin
|
273
|
+
require_valid_command command
|
274
|
+
rescue InvalidCommandError => e
|
275
|
+
abort "#{e}. Use --help for more information"
|
276
|
+
end
|
277
|
+
say help_formatter.render_command(command)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
##
|
284
|
+
# Raises InvalidCommandError when a _command_ is not found.
|
285
|
+
|
286
|
+
def require_valid_command command = active_command
|
287
|
+
raise InvalidCommandError, 'invalid command', caller if command.nil?
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# Removes global _options_ from _args_. This prevents an invalid
|
292
|
+
# option error from occurring when options are parsed
|
293
|
+
# again for the command.
|
294
|
+
|
295
|
+
def remove_global_options options, args
|
296
|
+
# TODO: refactor with flipflop, please TJ ! have time to refactor me !
|
297
|
+
options.each do |option|
|
298
|
+
switches = option[:switches].dup
|
299
|
+
next if switches.empty?
|
300
|
+
|
301
|
+
if switchHasArg = switches.any? { |s| s =~ /[ =]/ }
|
302
|
+
switches.map! { |s| s[0, s.index('=') || s.index(' ') || s.length] }
|
303
|
+
end
|
304
|
+
|
305
|
+
past_switch, arg_removed = false, false
|
306
|
+
args.delete_if do |arg|
|
307
|
+
if switches.any? { |s| arg[0, s.length] == s }
|
308
|
+
arg_removed = !switchHasArg
|
309
|
+
past_switch = true
|
310
|
+
elsif past_switch && !arg_removed && arg !~ /^-/
|
311
|
+
arg_removed = true
|
312
|
+
else
|
313
|
+
arg_removed = true
|
314
|
+
false
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Parse global command options.
|
322
|
+
|
323
|
+
def parse_global_options
|
324
|
+
|
325
|
+
parser = options.inject(OptionParser.new) do |options, option|
|
326
|
+
options.on *option[:args], &global_option_proc(option[:switches], &option[:proc])
|
327
|
+
end
|
328
|
+
|
329
|
+
options = @args.dup
|
330
|
+
begin
|
331
|
+
parser.parse!(options)
|
332
|
+
rescue OptionParser::InvalidOption => e
|
333
|
+
# Remove the offending args and retry.
|
334
|
+
options = options.reject { |o| e.args.include?(o) }
|
335
|
+
retry
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
##
|
340
|
+
# Returns a proc allowing for commands to inherit global options.
|
341
|
+
# This functionality works whether a block is present for the global
|
342
|
+
# option or not, so simple switches such as --verbose can be used
|
343
|
+
# without a block, and used throughout all commands.
|
344
|
+
|
345
|
+
def global_option_proc switches, &block
|
346
|
+
lambda do |value|
|
347
|
+
unless active_command.nil?
|
348
|
+
active_command.proxy_options << [Runner.switch_to_sym(switches.last), value]
|
349
|
+
end
|
350
|
+
yield value if block and !value.nil?
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
##
|
355
|
+
# Raises a CommandError when the program any of the _keys_ are not present, or empty.
|
356
|
+
|
357
|
+
def require_program *keys
|
358
|
+
keys.each do |key|
|
359
|
+
raise CommandError, "program #{key} required" if program(key).nil? or program(key).empty?
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
##
|
364
|
+
# Return switches and description separated from the _args_ passed.
|
365
|
+
|
366
|
+
def self.separate_switches_from_description *args
|
367
|
+
switches = args.find_all { |arg| arg.to_s =~ /^-/ }
|
368
|
+
description = args.last unless !args.last.is_a? String or args.last.match(/^-/)
|
369
|
+
return switches, description
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# Attempts to generate a method name symbol from +switch+.
|
374
|
+
# For example:
|
375
|
+
#
|
376
|
+
# -h # => :h
|
377
|
+
# --trace # => :trace
|
378
|
+
# --some-switch # => :some_switch
|
379
|
+
# --[no-]feature # => :feature
|
380
|
+
# --file FILE # => :file
|
381
|
+
# --list of,things # => :list
|
382
|
+
#
|
383
|
+
|
384
|
+
def self.switch_to_sym switch
|
385
|
+
switch.scan(/[\-\]](\w+)/).join('_').to_sym rescue nil
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# Run the active command.
|
390
|
+
|
391
|
+
def run_active_command
|
392
|
+
require_valid_command
|
393
|
+
if alias? command_name_from_args
|
394
|
+
active_command.run *(@aliases[command_name_from_args.to_s] + args_without_command_name)
|
395
|
+
else
|
396
|
+
active_command.run *args_without_command_name
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def say *args #:nodoc:
|
401
|
+
$terminal.say *args
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taco_it
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -35,18 +35,36 @@ executables:
|
|
35
35
|
extensions: []
|
36
36
|
extra_rdoc_files: []
|
37
37
|
files:
|
38
|
-
- lib/taco.rb
|
39
38
|
- lib/taco/change.rb
|
40
39
|
- lib/taco/cli.rb
|
40
|
+
- lib/taco/commander/blank.rb
|
41
|
+
- lib/taco/commander/command.rb
|
42
|
+
- lib/taco/commander/core_ext/array.rb
|
43
|
+
- lib/taco/commander/core_ext/object.rb
|
44
|
+
- lib/taco/commander/core_ext.rb
|
45
|
+
- lib/taco/commander/delegates.rb
|
46
|
+
- lib/taco/commander/help_formatters/base.rb
|
47
|
+
- lib/taco/commander/help_formatters/terminal/command_help.erb
|
48
|
+
- lib/taco/commander/help_formatters/terminal/help.erb
|
49
|
+
- lib/taco/commander/help_formatters/terminal.rb
|
50
|
+
- lib/taco/commander/help_formatters/terminal_compact/command_help.erb
|
51
|
+
- lib/taco/commander/help_formatters/terminal_compact/help.erb
|
52
|
+
- lib/taco/commander/help_formatters/terminal_compact.rb
|
53
|
+
- lib/taco/commander/help_formatters.rb
|
54
|
+
- lib/taco/commander/import.rb
|
55
|
+
- lib/taco/commander/platform.rb
|
56
|
+
- lib/taco/commander/runner.rb
|
57
|
+
- lib/taco/commander/version.rb
|
41
58
|
- lib/taco/commander.rb
|
59
|
+
- lib/taco/defaults/index.html.erb
|
60
|
+
- lib/taco/defaults/taco_profile
|
61
|
+
- lib/taco/defaults/tacorc
|
42
62
|
- lib/taco/issue.rb
|
43
63
|
- lib/taco/schema.rb
|
44
64
|
- lib/taco/taco.rb
|
45
65
|
- lib/taco/taco_profile.rb
|
46
66
|
- lib/taco/tacorc.rb
|
47
|
-
- lib/taco
|
48
|
-
- lib/taco/defaults/taco_profile
|
49
|
-
- lib/taco/defaults/tacorc
|
67
|
+
- lib/taco.rb
|
50
68
|
- bin/taco
|
51
69
|
homepage: http://github.com/mikepartelow/taco
|
52
70
|
licenses: []
|