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.
@@ -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