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.
@@ -1,34 +1,230 @@
1
1
 
2
+ require 'optparse'
3
+ require 'ostruct'
4
+
2
5
  module Commander
3
- class Command
4
-
5
- attr_accessor :description, :syntax, :examples, :options, :command, :manual
6
- attr_reader :when_called_proc
7
-
8
- def initialize(command)
9
- @command, @options, @examples = command, [], []
10
- end
11
-
12
- # Adds an example in the help documentation of this sub-command.
13
- def example(description, code)
14
- @examples << { :description => description, :code => code }
15
- end
16
-
17
- # Adds an option or 'flag'. These are parsed with +OptionParser+
18
- # so please view its documentation for help.
19
- def option(*args, &block)
20
- @options << { :args => args, :proc => block }
21
- end
22
-
23
- # This proc is called when the sub-command is invoked.
24
- # The proc has instant access to all methods found in
25
- # interaction.rb
26
- def when_called(&block)
27
- @when_called_proc = block
28
- end
29
-
30
- def invoke(method_name, *args)
31
- send(method_name).call *args
32
- end
33
- end
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,12 @@
1
+
2
+ require 'forwardable'
3
+
4
+ ##
5
+ # Make the following methods globally accessable:
6
+ #
7
+ # * HighLine#color
8
+
9
+ module Kernel
10
+ extend Forwardable
11
+ def_delegators :$terminal, :color
12
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class Object
3
+
4
+ include Commander::UI
5
+
6
+ ##
7
+ # Used with ERB in Commander::HelpFormatter::Base.
8
+
9
+ def get_binding
10
+ binding
11
+ end
12
+
13
+ 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,5 @@
1
+
2
+ require 'commander/core_ext/kernel'
3
+ require 'commander/core_ext/array'
4
+ require 'commander/core_ext/object'
5
+ require 'commander/core_ext/string'
@@ -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,6 @@
1
+
2
+ require 'commander/help_formatters/base'
3
+
4
+ module Commander::HelpFormatter
5
+ autoload :Terminal, 'commander/help_formatters/terminal'
6
+ 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