commander-openflighthpc 1.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +44 -0
  5. data/.rubocop_todo.yml +77 -0
  6. data/.travis.yml +14 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +51 -0
  10. data/Manifest +38 -0
  11. data/README.md +492 -0
  12. data/Rakefile +13 -0
  13. data/bin/commander +104 -0
  14. data/commander-openflighthpc.gemspec +32 -0
  15. data/lib/commander.rb +36 -0
  16. data/lib/commander/blank.rb +7 -0
  17. data/lib/commander/command.rb +263 -0
  18. data/lib/commander/configure.rb +14 -0
  19. data/lib/commander/core_ext.rb +2 -0
  20. data/lib/commander/core_ext/array.rb +24 -0
  21. data/lib/commander/core_ext/object.rb +8 -0
  22. data/lib/commander/delegates.rb +27 -0
  23. data/lib/commander/help_formatters.rb +52 -0
  24. data/lib/commander/help_formatters/base.rb +24 -0
  25. data/lib/commander/help_formatters/terminal.rb +24 -0
  26. data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
  27. data/lib/commander/help_formatters/terminal/help.erb +36 -0
  28. data/lib/commander/help_formatters/terminal/subcommand_help.erb +23 -0
  29. data/lib/commander/help_formatters/terminal_compact.rb +11 -0
  30. data/lib/commander/help_formatters/terminal_compact/command_help.erb +26 -0
  31. data/lib/commander/help_formatters/terminal_compact/help.erb +29 -0
  32. data/lib/commander/help_formatters/terminal_compact/subcommand_help.erb +15 -0
  33. data/lib/commander/import.rb +5 -0
  34. data/lib/commander/methods.rb +11 -0
  35. data/lib/commander/patches/decimal-integer.rb +17 -0
  36. data/lib/commander/patches/help_formatter_binding.rb +15 -0
  37. data/lib/commander/patches/implicit-short-tags.rb +75 -0
  38. data/lib/commander/patches/option_defaults.rb +23 -0
  39. data/lib/commander/patches/validate_inputs.rb +76 -0
  40. data/lib/commander/platform.rb +7 -0
  41. data/lib/commander/runner.rb +493 -0
  42. data/lib/commander/user_interaction.rb +551 -0
  43. data/lib/commander/version.rb +3 -0
  44. data/spec/command_spec.rb +157 -0
  45. data/spec/configure_spec.rb +37 -0
  46. data/spec/core_ext/array_spec.rb +18 -0
  47. data/spec/core_ext/object_spec.rb +19 -0
  48. data/spec/help_formatters/terminal_compact_spec.rb +69 -0
  49. data/spec/help_formatters/terminal_spec.rb +67 -0
  50. data/spec/methods_spec.rb +61 -0
  51. data/spec/patches/validate_inputs_spec.rb +84 -0
  52. data/spec/runner_spec.rb +672 -0
  53. data/spec/spec_helper.rb +79 -0
  54. data/spec/ui_spec.rb +30 -0
  55. metadata +183 -0
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ desc 'Run specs'
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.verbose = false
8
+ t.rspec_opts = '--color --order random'
9
+ end
10
+
11
+ RuboCop::RakeTask.new
12
+
13
+ task default: [:spec, :rubocop]
data/bin/commander ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'commander/import'
5
+
6
+ program :name, 'commander'
7
+ program :version, Commander::VERSION
8
+ program :description, 'Commander utility program.'
9
+
10
+ command :init do |c|
11
+ c.syntax = 'commander init [option] <file>'
12
+ c.summary = 'Initialize a commander template'
13
+ c.description = 'Initialize an empty <file> with a commander template,
14
+ allowing very quick creation of commander executables.'
15
+ c.example 'Create a new classic style template file.', 'commander init bin/my_executable'
16
+ c.example 'Create a new modular style template file.', 'commander init --modular bin/my_executable'
17
+ c.option '-m', '--modular', 'Initialize a modular style template'
18
+ c.action do |args, options|
19
+ file = args.shift || abort('file argument required.')
20
+ name = ask 'Machine name of program: '
21
+ description = ask 'Describe your program: '
22
+ commands = ask_for_array 'List the commands you wish to create: '
23
+ begin
24
+ if options.modular
25
+ File.open(file, 'w') do |f|
26
+ f.write <<-"...".gsub!(/^ {10}/, '')
27
+ #!/usr/bin/env ruby
28
+
29
+ require 'rubygems'
30
+ require 'commander'
31
+
32
+ class MyApplication
33
+ include Commander::Methods
34
+ # include whatever modules you need
35
+
36
+ def run
37
+ program :name, '#{name}'
38
+ program :version, '0.0.1'
39
+ program :description, '#{description}'
40
+
41
+ ...
42
+ commands.each do |command|
43
+ f.write <<-"...".gsub!(/^ {12}/, '')
44
+ command :#{command} do |c|
45
+ c.syntax = '#{name} #{command} [options]'
46
+ c.summary = ''
47
+ c.description = ''
48
+ c.example 'description', 'command example'
49
+ c.option '--some-switch', 'Some switch that does something'
50
+ c.action do |args, options|
51
+ # Do something or c.when_called #{name.capitalize}::Commands::#{command.capitalize}
52
+ end
53
+ end
54
+
55
+ ...
56
+ end
57
+ f.write <<-"...".gsub!(/^ {12}/, '')
58
+ run!
59
+ end
60
+ end
61
+
62
+ MyApplication.new.run if $0 == __FILE__
63
+ ...
64
+ end
65
+
66
+ File.chmod(0755, file)
67
+ say "Initialized modular template in #{file}"
68
+ else
69
+ File.open(file, 'w') do |f|
70
+ f.write <<-"...".gsub!(/^ {10}/, '')
71
+ #!/usr/bin/env ruby
72
+
73
+ require 'rubygems'
74
+ require 'commander/import'
75
+
76
+ program :name, '#{name}'
77
+ program :version, '0.0.1'
78
+ program :description, '#{description}'
79
+
80
+ ...
81
+ commands.each do |command|
82
+ f.write <<-"...".gsub!(/^ {12}/, '')
83
+ command :#{command} do |c|
84
+ c.syntax = '#{name} #{command} [options]'
85
+ c.summary = ''
86
+ c.description = ''
87
+ c.example 'description', 'command example'
88
+ c.option '--some-switch', 'Some switch that does something'
89
+ c.action do |args, options|
90
+ # Do something or c.when_called #{name.capitalize}::Commands::#{command.capitalize}
91
+ end
92
+ end
93
+
94
+ ...
95
+ end
96
+ end
97
+ File.chmod 0755, file
98
+ say "Initialized template in #{file}"
99
+ end
100
+ rescue => e
101
+ abort e
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
4
+ require 'commander/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'commander-openflighthpc'
8
+ s.version = Commander::VERSION
9
+ s.authors = ['Alces Flight Ltd', 'TJ Holowaychuk', 'Gabriel Gilder']
10
+ s.email = ['flight@openflighthpc.org']
11
+ s.license = 'MIT'
12
+ s.homepage = 'https://github.com/openflighthpc/commander-openflighthpc'
13
+ s.summary = 'The complete solution for Ruby command-line executables'
14
+ s.description = 'The complete solution for Ruby command-line executables. Commander bridges the gap between other terminal related libraries you know and love (OptionParser, HighLine), while providing many new features, and an elegant API.'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.require_paths = ['lib']
20
+
21
+ s.add_runtime_dependency('highline', '~> 1.7.2')
22
+
23
+ s.add_development_dependency('rspec', '~> 3.2')
24
+ s.add_development_dependency('rake')
25
+ s.add_development_dependency('simplecov')
26
+ if RUBY_VERSION < '2.0'
27
+ s.add_development_dependency('rubocop', '~> 0.41.1')
28
+ s.add_development_dependency('json', '< 2.0')
29
+ else
30
+ s.add_development_dependency('rubocop', '~> 0.49.1')
31
+ end
32
+ end
data/lib/commander.rb ADDED
@@ -0,0 +1,36 @@
1
+ #--
2
+ # Copyright (c) 2008-2009 TJ Holowaychuk <tj@vision-media.ca>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'highline'
25
+ $terminal = HighLine.new
26
+ require 'commander/version'
27
+ require 'commander/blank'
28
+ require 'commander/user_interaction'
29
+ require 'commander/core_ext'
30
+ require 'commander/runner'
31
+ require 'commander/command'
32
+ require 'commander/help_formatters'
33
+ require 'commander/platform'
34
+ require 'commander/delegates'
35
+ require 'commander/methods'
36
+ require 'commander/configure'
@@ -0,0 +1,7 @@
1
+ module Blank
2
+ def self.included(base)
3
+ base.class_eval do
4
+ instance_methods.each { |m| undef_method m unless m =~ /^__|object_id/ }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,263 @@
1
+ require 'optparse'
2
+ require 'commander/patches/implicit-short-tags'
3
+ require 'commander/patches/decimal-integer'
4
+ require 'commander/patches/validate_inputs'
5
+ require 'commander/patches/option_defaults'
6
+ require 'commander/patches/help_formatter_binding'
7
+
8
+ OptionParser.prepend Commander::Patches::ImplicitShortTags
9
+ OptionParser.prepend Commander::Patches::DecimalInteger
10
+
11
+ module Commander
12
+ class Command
13
+ prepend Patches::ValidateInputs
14
+
15
+ attr_accessor :name, :examples, :syntax, :description
16
+ attr_accessor :summary, :proxy_options, :options, :hidden
17
+ attr_reader :sub_command_group
18
+
19
+ alias sub_command= hidden=
20
+ alias sub_command hidden
21
+
22
+ ##
23
+ # Options struct.
24
+
25
+ class Options
26
+ include Blank
27
+
28
+ def initialize
29
+ @table = {}
30
+ end
31
+
32
+ def __hash__
33
+ @table
34
+ end
35
+
36
+ def method_missing(meth, *args)
37
+ meth.to_s =~ /=$/ ? @table[meth.to_s.chop.to_sym] = args.first : @table[meth]
38
+ end
39
+
40
+ def default(defaults = {})
41
+ @table = defaults.merge! @table
42
+ end
43
+
44
+ def inspect
45
+ "<Commander::Command::Options #{ __hash__.map { |k, v| "#{k}=#{v.inspect}" }.join(', ') }>"
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Initialize new command with specified _name_.
51
+
52
+ def initialize(name)
53
+ @name, @examples, @when_called = name.to_s, [], []
54
+ @options, @proxy_options = [], []
55
+ end
56
+
57
+ ##
58
+ # Add a usage example for this command.
59
+ #
60
+ # Usage examples are later displayed in help documentation
61
+ # created by the help formatters.
62
+ #
63
+ # === Examples
64
+ #
65
+ # command :something do |c|
66
+ # c.example "Should do something", "my_command something"
67
+ # end
68
+ #
69
+
70
+ def example(description, command)
71
+ @examples << [description, command]
72
+ end
73
+
74
+ ##
75
+ # Add an option.
76
+ #
77
+ # Options are parsed via OptionParser so view it
78
+ # for additional usage documentation. A block may optionally be
79
+ # passed to handle the option, otherwise the _options_ struct seen below
80
+ # contains the results of this option. This handles common formats such as:
81
+ #
82
+ # -h, --help options.help # => bool
83
+ # --[no-]feature options.feature # => bool
84
+ # --large-switch options.large_switch # => bool
85
+ # --file FILE options.file # => file passed
86
+ # --list WORDS options.list # => array
87
+ # --date [DATE] options.date # => date or nil when optional argument not set
88
+ #
89
+ # === Examples
90
+ #
91
+ # command :something do |c|
92
+ # c.option '--recursive', 'Do something recursively'
93
+ # c.option '--file FILE', 'Specify a file'
94
+ # c.option('--info', 'Display info') { puts "handle with block" }
95
+ # c.option '--[no-]feature', 'With or without feature'
96
+ # c.option '--list FILES', Array, 'List the files specified'
97
+ #
98
+ # c.when_called do |args, options|
99
+ # do_something_recursively if options.recursive
100
+ # do_something_with_file options.file if options.file
101
+ # end
102
+ # end
103
+ #
104
+ # === Help Formatters
105
+ #
106
+ # This method also parses the arguments passed in order to determine
107
+ # which were switches, and which were descriptions for the
108
+ # option which can later be used within help formatters
109
+ # using option[:switches] and option[:description].
110
+ #
111
+ # === Input Parsing
112
+ #
113
+ # Since Commander utilizes OptionParser you can pre-parse and evaluate
114
+ # option arguments. Simply require 'optparse/time', or 'optparse/date', as these
115
+ # objects must respond to #parse.
116
+ #
117
+ # c.option '--time TIME', Time
118
+ # c.option '--date [DATE]', Date
119
+ #
120
+
121
+ # NOTE: This method is being patched to handle defaults differently
122
+ prepend Patches::OptionDefaults
123
+ def option(*args, &block)
124
+ switches, description = Runner.separate_switches_from_description(*args)
125
+ proc = block || option_proc(switches)
126
+ @options << {
127
+ args: args,
128
+ proc: proc,
129
+ switches: switches,
130
+ description: description,
131
+ }
132
+ end
133
+
134
+ ##
135
+ # Handle execution of command. The handler may be a class,
136
+ # object, or block (see examples below).
137
+ #
138
+ # === Examples
139
+ #
140
+ # # Simple block handling
141
+ # c.when_called do |args, options|
142
+ # # do something
143
+ # end
144
+ #
145
+ # # Create inst of Something and pass args / options
146
+ # c.when_called MyLib::Command::Something
147
+ #
148
+ # # Create inst of Something and use arbitrary method
149
+ # c.when_called MyLib::Command::Something, :some_method
150
+ #
151
+ # # Pass an object to handle callback (requires method symbol)
152
+ # c.when_called SomeObject, :some_method
153
+ #
154
+
155
+ def when_called(*args, &block)
156
+ fail ArgumentError, 'must pass an object, class, or block.' if args.empty? && !block
157
+ @when_called = block ? [block] : args
158
+ end
159
+ alias action when_called
160
+
161
+ ##
162
+ # Handles displaying subcommand help. By default it will set the action to
163
+ # display the subcommand if the action hasn't already been set
164
+
165
+ def sub_command_group?
166
+ !!@sub_command_group
167
+ end
168
+
169
+ def sub_command_group=(value)
170
+ @sub_command_group = value
171
+ if @when_called.empty?
172
+ self.action {
173
+ exec("#{$0} #{ARGV.join(" ")} --help")
174
+ }
175
+ end
176
+ end
177
+
178
+ def configure_sub_command(runner)
179
+ @sub_command_group = true
180
+ if @when_called.empty?
181
+ action do |args, opts|
182
+ unless args.empty?
183
+ raise Commander::Runner::InvalidCommandError,
184
+ "unrecognized subcommand '#{args[0]}'"
185
+ end
186
+ runner.command('help').run(ARGV[0])
187
+ end
188
+ end
189
+ end
190
+
191
+ ##
192
+ # Run the command with _args_.
193
+ #
194
+ # * parses options, call option blocks
195
+ # * invokes when_called proc
196
+ #
197
+
198
+ def run(*args)
199
+ call parse_options_and_call_procs(*args)
200
+ end
201
+
202
+ #:stopdoc:
203
+
204
+ ##
205
+ # Parses options and calls associated procs,
206
+ # returning the arguments remaining.
207
+
208
+ def parse_options_and_call_procs(*args)
209
+ opt = @options.each_with_object(OptionParser.new) do |option, opts|
210
+ opts.on(*option[:args], &option[:proc])
211
+ opts
212
+ end
213
+ default_opt = @options.each_with_object([]) do |h, arr|
214
+ if h.key?(:default)
215
+ arr.push(h[:switches][0].split[0])
216
+ arr.push(h[:default].to_s)
217
+ end
218
+ end
219
+ opt.parse! default_opt
220
+ opt.parse! args
221
+ end
222
+
223
+ ##
224
+ # Call the commands when_called block with _args_.
225
+
226
+ def call(args = [])
227
+ object = @when_called.shift
228
+ meth = @when_called.shift || :call
229
+ options = proxy_option_struct
230
+ case object
231
+ when Proc then object.call(args, options)
232
+ when Class then meth != :call ? object.new.send(meth, args, options) : object.new(args, options)
233
+ else object.send(meth, args, options) if object
234
+ end
235
+ end
236
+
237
+ ##
238
+ # Creates an Options instance populated with the option values
239
+ # collected by the #option_proc.
240
+
241
+ def proxy_option_struct
242
+ proxy_options.each_with_object(Options.new) do |(option, value), options|
243
+ # options that are present will evaluate to true
244
+ value = true if value.nil?
245
+ options.__send__ :"#{option}=", value
246
+ options
247
+ end
248
+ end
249
+
250
+ ##
251
+ # Option proxy proc used when a block is not explicitly passed
252
+ # via the #option method. This allows commander to auto-populate
253
+ # and work with option values.
254
+
255
+ def option_proc(switches)
256
+ ->(value) { proxy_options << [Runner.switch_to_sym(switches.last), value] }
257
+ end
258
+
259
+ def inspect
260
+ "<Commander::Command:#{name}>"
261
+ end
262
+ end
263
+ end