nub 0.0.55 → 0.0.56
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.
- checksums.yaml +4 -4
- data/README.md +80 -54
- data/lib/nub/commander.rb +364 -195
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6840a65c5b757eb9cf92cdbfd1548577f2a0abf4b91470477cb133a048259372
|
4
|
+
data.tar.gz: 3c5dd5de7ce42a2a2ba91b4ac2d5aa6cf773cda0320723bdd13f79ddbd918de8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20051bea8cd2af4aa22c09683673e3a99c6deb0b3e166c6e18175c7e87d96035ce3497b38337de3956274879ad52a47cb172bd80eee7f3be7b2030278c4ddb84
|
7
|
+
data.tar.gz: eddef00f72ad07ab8b384317113633d5256d489221f8411128e0db86dbee7da51c514264b93c8c3d749f0ae391d9d2f4d93396fd5806988c254118f44bc14f12
|
data/README.md
CHANGED
@@ -10,53 +10,95 @@ Collection of ruby utils I've used in several of my projects and wanted re-usabl
|
|
10
10
|
### Table of Contents
|
11
11
|
* [Deploy](#deploy)
|
12
12
|
* [Commander](#commander)
|
13
|
-
|
14
|
-
* [
|
15
|
-
* [
|
13
|
+
* [Commands](#commands)
|
14
|
+
* [Command Parameters ](#command-paramaters)
|
15
|
+
* [Chained Commands](#chained-commands)
|
16
|
+
* [Options](#options)
|
17
|
+
* [Positional Options](#positional-options)
|
18
|
+
* [Value Types](#value-types)
|
19
|
+
* [Allowed Values](#allowed-values)
|
20
|
+
* [Global Options](#global-options)
|
21
|
+
* [Configuration](#configuration)
|
22
|
+
* [Help](#help)
|
23
|
+
* [Examples](#examples)
|
24
|
+
* [Indicators](#indicators)
|
16
25
|
* [Config](#config)
|
17
26
|
* [Ruby Gem Creation](#ruby-gem-creation)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
27
|
+
* [Package Layout](#package-layout)
|
28
|
+
* [Build Gem](#build-gem)
|
29
|
+
* [Install Gem](#install-gem)
|
30
|
+
* [Push Gem](#push-gem)
|
22
31
|
* [Integrate with Travis-CI](#integrate-with-travis-ci)
|
23
|
-
|
24
|
-
|
32
|
+
* [Install Travis Client](#install-travis-client)
|
33
|
+
* [Deploy Ruby Gem on Tag](#deploy-ruby-gem-on-tag)
|
25
34
|
|
26
35
|
## Deploy <a name="deploy"></a>
|
27
36
|
Run: `bundle install --system`
|
28
37
|
|
29
38
|
## Commander <a name="commander"></a>
|
30
|
-
Commander was created mainly because all available options parsers seemed complicated and
|
31
|
-
and partly because I enjoyed understanding every bit going into it. Commander offers
|
32
|
-
command syntax that is becoming so popular.
|
33
|
-
faster to type.
|
39
|
+
Commander was created mainly because all available options parsers seemed overly complicated and
|
40
|
+
overweight and partly because I enjoyed understanding every bit going into it. Commander offers
|
41
|
+
***git*** like command syntax that is becoming so popular.
|
34
42
|
|
35
43
|
There are two kinds of paramaters that commander deals with ***commands*** and ***options***.
|
36
|
-
Commands are specific named parameters that may or may not have options specific to it. Commands
|
37
|
-
have their own help to display their usage and available options.
|
38
44
|
|
39
45
|
### Commands <a name="commands"></a>
|
40
|
-
Commands are
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
allows one to have a cleaner multi-command line expression with reusable options. Options are said
|
49
|
-
to apply in a chained command syntax when they are of the same type and position in the positional
|
50
|
-
case or same type and name in the named case.
|
46
|
+
Commands are specific named parameters that may or may not have options specific to it. Commands
|
47
|
+
have their own help to display their usage and available options. Commands are used to trigger
|
48
|
+
different branches of functionality in an application.
|
49
|
+
|
50
|
+
#### Command Parameters <a name="command-parameters"></a>
|
51
|
+
Each command may have zero or more command parameters. Command parameters may be either a
|
52
|
+
sub-command, which follow the same rules in a recursive fashion as any command, or an option.
|
53
|
+
Command options modify how the command behaves.
|
51
54
|
|
55
|
+
#### Chained Commands <a name="chained-commands"></a>
|
56
|
+
The chained command expressions allow a cleaner multi-command type expression with reusable options.
|
57
|
+
|
58
|
+
Whenever more than one command is used in the command line expression the expression is interpreted
|
59
|
+
as being a ***chained command expression*** a.k.a ***chained commands***. Chained commands are
|
60
|
+
executed left to right, such that you can execute the first command then the second command or more
|
61
|
+
in a single command line expression. Each command in a chained command expression may have its own
|
62
|
+
specific options (those coming after the command but before the next command) or if options are
|
63
|
+
omitted the options from the next command will be used in the order they are given to satisfy the
|
64
|
+
options of the command before. Only options of the same type and position will be used.
|
65
|
+
|
66
|
+
### Options <a name="options"></a>
|
67
|
+
Options are additional parameters that are given that modify the behavior of a command. There are
|
68
|
+
two kinds of options available for use, ***positional*** and ***named***.
|
69
|
+
|
70
|
+
#### Positional Options <a name="positional-options"></a>
|
71
|
+
Positional options are identified by the absence of preceding dash/dashes and are interpreted
|
72
|
+
according to the order in which they were found. Positional options always pass a value into the
|
73
|
+
application. Positional options are named internally with the command name concatted with a zero
|
74
|
+
based int representing its order ***e.g. clean0*** where *clean* is the command name and *0* is the
|
75
|
+
positional options order given during configuration. Positional options are given sequentially so
|
76
|
+
you can't skip one and specify the second, it must be one then two etc...
|
77
|
+
|
78
|
+
#### Named Options
|
79
|
+
Named options have a name that is prefixed with a dash (short hand) or two dashes (long hand) e.g.
|
80
|
+
***-h*** or ***--help*** and may be a value passed in or simply a boolean flag. **Long Hand** form
|
81
|
+
is always required for named options, short hand may or may not be given. An incoming
|
82
|
+
**value/values** are indicated by the hint configuration e.g. ***-s|--skip=COMPONENTS*** indicates
|
83
|
+
there is an incoming value/values to be expected because of the hint ***COMPONENTS***.
|
84
|
+
|
85
|
+
#### Value Types <a name="value-types"></a>
|
86
|
+
Option values require a ***type*** so that Commander can interpret how to use them. The supported
|
87
|
+
value types are ***true, false, Integer, String, Array***. Positional options default to
|
88
|
+
type String while named options default to false. The named option flag default of false can be
|
89
|
+
changed to default to true by setting the ***type:true*** configuration param.
|
90
|
+
|
91
|
+
#### Allowed Values <a name="allowed-values"></a>
|
92
|
+
Commander will check the values given against an allowed list if so desired. This is done via the
|
93
|
+
***allowed*** configuration parameter.
|
94
|
+
|
95
|
+
#### Global Options <a name="global-options"></a>
|
52
96
|
***Global*** options are options that are added with the ***add_global*** function and will show up
|
53
97
|
set in the command results using the ***:global*** symbol. Global positional options must be given
|
54
98
|
before any other commands but global named options may appear anywhere in the command line
|
55
99
|
expression.
|
56
100
|
|
57
|
-
|
58
|
-
should be added before any commands are added. They are added to each command as an explicit option.
|
59
|
-
|
101
|
+
### Configuration <a name="configuration"></a>
|
60
102
|
***Commander.new*** must be run from the app's executable file for it to pick up the app's filename
|
61
103
|
properly.
|
62
104
|
|
@@ -98,24 +140,6 @@ Example command line expressions:
|
|
98
140
|
./app clean all build all
|
99
141
|
```
|
100
142
|
|
101
|
-
### Options <a name="options"></a>
|
102
|
-
There are two kinds of options available for use, ***positional*** and ***named***. Positional
|
103
|
-
options are identified by the absence of preceding dash/dashes and interpreted according to the
|
104
|
-
order and number in which they were found. Positional options are a value being passed into the
|
105
|
-
application. Named options have a name that is prefixed with a dash (short hand) or two dashes
|
106
|
-
(long hand) e.g. ***-h*** or ***--help*** and may simply be a flag or pass in a value. Option
|
107
|
-
values require a ***type*** so that Commander can interpret how to use them. The supported value
|
108
|
-
types are ***Flag, Integer, String, Array***. Values may be checked or not checked via the
|
109
|
-
***allowed*** config param. Positional options default to type String while named options default to
|
110
|
-
type Flag. Positional options are named internally with the command concatted with a an int for
|
111
|
-
order ***e.g. clean0*** zero based. Positional options are given sequentially so you can't
|
112
|
-
skip one and specify the second, it must be one then two etc...
|
113
|
-
|
114
|
-
**Long Hand** form is always required for named options, short hand may or may not be given.
|
115
|
-
|
116
|
-
**Values** are indicated by the hint given e.g. ***-s|--skip=COMPONENTS*** indicates there is an
|
117
|
-
incoming value/values to be expected because of the hint ***COMPONENTS***.
|
118
|
-
|
119
143
|
Example ruby configuration:
|
120
144
|
```ruby
|
121
145
|
# Creates a new instance of commander with app settings as given
|
@@ -155,10 +179,15 @@ Example command line expressions:
|
|
155
179
|
### Help <a name="help"></a>
|
156
180
|
Help for your appliation and commands is automatically supported with the ***-h*** and ***--help***
|
157
181
|
flags and is generated from the app ***name***, ***version***, ***examples***, ***commands***,
|
158
|
-
***descriptions*** and ***options*** given in Commander's configuration.
|
159
|
-
|
160
|
-
|
161
|
-
|
182
|
+
***descriptions*** and ***options*** given in Commander's configuration.
|
183
|
+
|
184
|
+
#### Examples <a name="examples"></a>
|
185
|
+
Examples is just a free form string that is displayed before usage so user's have an idea of how to
|
186
|
+
put together the commands and options.
|
187
|
+
|
188
|
+
#### Indicators <a name="indicators"></a>
|
189
|
+
Allowed checks, types, and required flags specified in the configuration are known as indicators in
|
190
|
+
the help context and are added to the end of the option descriptions.
|
162
191
|
|
163
192
|
Example ruby configuration:
|
164
193
|
```ruby
|
@@ -235,9 +264,6 @@ Usage: ./builder build [options]
|
|
235
264
|
-h|--help Print command/options help
|
236
265
|
```
|
237
266
|
|
238
|
-
**Required**
|
239
|
-
Options can be required using the ***required:true*** options param
|
240
|
-
|
241
267
|
## Config <a name="config"></a>
|
242
268
|
Config is a simple YAML wrapper with some extra features. Since it implements the ***Singleton***
|
243
269
|
pattern you can easily use it through out your app without carrying around instances everywhere.
|
data/lib/nub/commander.rb
CHANGED
@@ -18,8 +18,8 @@
|
|
18
18
|
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
19
|
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
20
|
#SOFTWARE.
|
21
|
-
|
22
21
|
require 'colorize'
|
22
|
+
require 'ostruct'
|
23
23
|
require_relative 'log'
|
24
24
|
require_relative 'sys'
|
25
25
|
require_relative 'string'
|
@@ -34,26 +34,26 @@ class Option
|
|
34
34
|
attr_reader(:type)
|
35
35
|
attr_accessor(:allowed)
|
36
36
|
attr_accessor(:required)
|
37
|
-
attr_accessor(:shared)
|
38
37
|
|
39
38
|
# Create a new option instance
|
40
39
|
# @param key [String] option short hand, long hand and hint e.g. -s|--skip=COMPONENTS
|
41
40
|
# @param desc [String] the option's description
|
42
41
|
# @param type [Type] the option's type
|
43
42
|
# @param required [Bool] require the option if true else optional
|
44
|
-
# @param allowed [
|
45
|
-
def initialize(key, desc, type:nil, required:false, allowed:
|
43
|
+
# @param allowed [Hash] hash of allowed strings to descriptions maps
|
44
|
+
def initialize(key, desc, type:nil, required:false, allowed:{})
|
46
45
|
@hint = nil
|
47
46
|
@long = nil
|
48
47
|
@short = nil
|
49
48
|
@desc = desc
|
50
|
-
@
|
51
|
-
@allowed = allowed || []
|
49
|
+
@allowed = allowed || {}
|
52
50
|
@required = required || false
|
51
|
+
Log.die("allowed should be a hash of values to descriptions") if allowed.class != Hash
|
52
|
+
Log.die("required should be a boolean value") if ![TrueClass, FalseClass].include?(required.class)
|
53
53
|
|
54
54
|
# Parse the key into its components (short hand, long hand, and hint)
|
55
55
|
#https://bneijt.nl/pr/ruby-regular-expressions/
|
56
|
-
# Valid forms to look for with chars [a-zA-Z0-9-_=|]
|
56
|
+
# Valid forms to look for with chars [a-zA-Z0-9-_=|]
|
57
57
|
# --help, --help=HINT, -h|--help, -h|--help=HINT
|
58
58
|
Log.die("invalid option key #{key}") if key && (key.count('=') > 1 or key.count('|') > 1 or !key[/[^\w\-=|]/].nil? or
|
59
59
|
key[/(^--[a-zA-Z0-9\-_]+$)|(^--[a-zA-Z\-_]+=\w+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+=\w+$)/].nil?)
|
@@ -64,22 +64,72 @@ class Option
|
|
64
64
|
@long = key[/(--[\w\-]+)(=.+)*$/, 1]
|
65
65
|
end
|
66
66
|
|
67
|
-
#
|
68
|
-
|
67
|
+
# Convert true/false to TrueClass/FalseClass
|
68
|
+
type = TrueClass if type.class == TrueClass
|
69
|
+
type = FalseClass if type.class == FalseClass
|
70
|
+
|
71
|
+
# Validate and set type, allow Flag defaults to be true or false
|
72
|
+
Log.die("invalid option type #{type}") if ![String, Integer, Array, TrueClass, FalseClass, nil].any?{|x| type == x}
|
69
73
|
Log.die("option type must be set") if @hint && !type
|
70
74
|
@type = String if !key && !type
|
71
75
|
@type = FalseClass if key and !type
|
72
76
|
@type = type if type
|
73
77
|
|
74
78
|
# Validate hint is given for non flags
|
75
|
-
Log.die("option hint must be set") if @key && !@hint && @type != FalseClass
|
79
|
+
Log.die("option hint must be set") if @key && !@hint && @type != FalseClass && @type != TrueClass
|
76
80
|
|
77
81
|
# Validate allowed
|
78
82
|
if @allowed.any?
|
79
|
-
allowed_type = @allowed.first.class
|
80
|
-
Log.die("mixed allowed types") if @allowed.any?{|
|
83
|
+
allowed_type = @allowed.first.first.class
|
84
|
+
Log.die("mixed allowed types") if @allowed.any?{|k,v| k.class != allowed_type}
|
81
85
|
end
|
82
86
|
end
|
87
|
+
|
88
|
+
# Get a symbol representing the command
|
89
|
+
# @returns symbol
|
90
|
+
def to_sym
|
91
|
+
return @long[2..-1].gsub("-", "_").to_sym
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return a human readable string of this object
|
95
|
+
# @param level [Integer] level to indent
|
96
|
+
def to_s(level:0)
|
97
|
+
return "#{" " * level * 2}Option => key:#{@key}, desc:'#{@desc}', type:#{@type}, allowed:#{@allowed}, required:#{@required}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Command
|
102
|
+
attr_reader(:name)
|
103
|
+
attr_reader(:desc)
|
104
|
+
attr_accessor(:nodes)
|
105
|
+
attr_accessor(:help)
|
106
|
+
|
107
|
+
# Create a new command
|
108
|
+
# @param name [String] command name used on command line
|
109
|
+
# @param desc [String] the command's description
|
110
|
+
# @param nodes [String] the command's description
|
111
|
+
def initialize(name, desc, nodes:[])
|
112
|
+
@name = name
|
113
|
+
@desc = desc
|
114
|
+
@nodes = nodes
|
115
|
+
@help = ""
|
116
|
+
end
|
117
|
+
|
118
|
+
# Get a symbol representing the command
|
119
|
+
# @returns symbol
|
120
|
+
def to_sym
|
121
|
+
return @name.gsub('-', '_').to_sym
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return a human readable string of this object
|
125
|
+
# @param level [Integer] level to indent
|
126
|
+
def to_s(level:0)
|
127
|
+
str = "#{" " * level * 2}Command => name:#{@name}, desc:'#{@desc}'"
|
128
|
+
@nodes.each{|x|
|
129
|
+
str += "\n#{x.to_s(level: level + 1)}"
|
130
|
+
}
|
131
|
+
return str
|
132
|
+
end
|
83
133
|
end
|
84
134
|
|
85
135
|
# An implementation of git like command syntax for ruby applications:
|
@@ -89,13 +139,15 @@ class Commander
|
|
89
139
|
attr_reader(:banner)
|
90
140
|
attr_accessor(:cmds)
|
91
141
|
|
92
|
-
Command = Struct.new(:name, :desc, :opts, :help)
|
93
|
-
|
94
142
|
# Initialize the commands for your application
|
95
143
|
# @param app [String] application name e.g. reduce
|
96
144
|
# @param version [String] version of the application e.g. 1.0.0
|
97
145
|
# @param examples [String] optional examples to list after the title before usage
|
98
146
|
def initialize(app:nil, version:nil, examples:nil)
|
147
|
+
@k = OpenStruct.new({
|
148
|
+
global: 'global'
|
149
|
+
})
|
150
|
+
|
99
151
|
@app = app
|
100
152
|
@app_default = Sys.caller_filename
|
101
153
|
@version = version
|
@@ -107,18 +159,15 @@ class Commander
|
|
107
159
|
@long_regex = /(--[\w\-]+)(=.+)*$/
|
108
160
|
@value_regex = /.*=(.*)$/
|
109
161
|
|
110
|
-
#
|
162
|
+
# Command line expression results
|
111
163
|
# {command_name => {}}
|
112
164
|
@cmds = {}
|
113
165
|
|
114
166
|
# Configuration - ordered list of commands
|
115
167
|
@config = []
|
116
168
|
|
117
|
-
# List of options that will be added to all commands
|
118
|
-
@shared = []
|
119
|
-
|
120
169
|
# Configure default global options
|
121
|
-
add_global(
|
170
|
+
add_global('-h|--help', 'Print command/options help')
|
122
171
|
end
|
123
172
|
|
124
173
|
# Hash like accessor for checking if a command or option is set
|
@@ -134,44 +183,41 @@ class Commander
|
|
134
183
|
# Add a command to the command list
|
135
184
|
# @param cmd [String] name of the command
|
136
185
|
# @param desc [String] description of the command
|
137
|
-
# @param
|
138
|
-
def add(cmd, desc,
|
139
|
-
Log.die("'global' is a reserved command name") if cmd ==
|
140
|
-
Log.die("'shared' is a reserved command name") if cmd == 'shared'
|
186
|
+
# @param nodes [List] list of command nodes (i.e. options or commands)
|
187
|
+
def add(cmd, desc, nodes:[])
|
188
|
+
Log.die("'#{@k.global}' is a reserved command name") if cmd == @k.global
|
141
189
|
Log.die("'#{cmd}' already exists") if @config.any?{|x| x.name == cmd}
|
142
|
-
Log.die("'help' is a reserved option name") if
|
143
|
-
|
144
|
-
|
145
|
-
|
190
|
+
Log.die("'help' is a reserved option name") if nodes.any?{|x| x.class == Option && !x.key.nil? && x.key.include?('help')}
|
191
|
+
Log.die("command names must be pure lowercase letters or hypen") if cmd =~ /[^a-z-]/
|
192
|
+
|
193
|
+
# Validate sub command key words
|
194
|
+
validate_subcmd = ->(subcmd){
|
195
|
+
subcmd.nodes = [] if !subcmd.nodes
|
196
|
+
Log.die("'#{@k.global}' is a reserved command name") if subcmd.name == @k.global
|
197
|
+
Log.die("'help' is a reserved option name") if subcmd.nodes.any?{|x| x.class == Option && !x.key.nil? && x.key.include?('help')}
|
198
|
+
Log.die("command names must be pure lowercase letters or hypen") if subcmd.name =~ /[^a-z-]/
|
199
|
+
subcmd.nodes.select{|x| x.class != Option}.each{|x| validate_subcmd.(x)}
|
200
|
+
}
|
201
|
+
nodes.select{|x| x.class != Option}.each{|x| validate_subcmd.(x)}
|
146
202
|
|
147
|
-
|
148
|
-
@config << cmd
|
203
|
+
@config << add_cmd(cmd, desc, nodes)
|
149
204
|
end
|
150
205
|
|
151
206
|
# Add global options (any option coming before all commands)
|
152
|
-
# @param
|
153
|
-
|
154
|
-
|
207
|
+
# @param key [String] option short hand, long hand and hint e.g. -s|--skip=COMPONENTS
|
208
|
+
# @param desc [String] the option's description
|
209
|
+
# @param type [Type] the option's type
|
210
|
+
# @param required [Bool] require the option if true else optional
|
211
|
+
# @param allowed [Hash] hash of allowed values to description map
|
212
|
+
def add_global(key, desc, type:nil, required:false, allowed:{})
|
213
|
+
options = [Option.new(key, desc, type:type, required:required, allowed:allowed)]
|
155
214
|
|
156
215
|
# Aggregate global options
|
157
|
-
if (global = @config.find{|x| x.name ==
|
158
|
-
global.
|
159
|
-
@config.reject!{|x| x.name ==
|
216
|
+
if (global = @config.find{|x| x.name == @k.global})
|
217
|
+
global.nodes.each{|x| options << x}
|
218
|
+
@config.reject!{|x| x.name == @k.global}
|
160
219
|
end
|
161
|
-
@config << add_cmd(
|
162
|
-
end
|
163
|
-
|
164
|
-
# Add shared option (options that are added to all commands)
|
165
|
-
# @param option/s [Array/Option] array or single option/s
|
166
|
-
def add_shared(options)
|
167
|
-
options = [options] if options.class == Option
|
168
|
-
options.each{|x|
|
169
|
-
Log.die("duplicate shared option '#{x.desc}' given") if @shared
|
170
|
-
.any?{|y| y.key == x.key && y.desc == x.desc && y.type == x.type}
|
171
|
-
x.shared = true
|
172
|
-
x.required = true
|
173
|
-
@shared << x
|
174
|
-
}
|
220
|
+
@config << add_cmd(@k.global, 'Global options:', options)
|
175
221
|
end
|
176
222
|
|
177
223
|
# Returns banner string
|
@@ -185,6 +231,8 @@ class Commander
|
|
185
231
|
# Return the app's help string
|
186
232
|
# @return [String] the app's help string
|
187
233
|
def help
|
234
|
+
|
235
|
+
# Global help
|
188
236
|
help = @app.nil? ? "" : "#{banner}\n"
|
189
237
|
if !@examples.nil? && !@examples.empty?
|
190
238
|
newline = @examples.strip_color[-1] != "\n" ? "\n" : ""
|
@@ -192,9 +240,9 @@ class Commander
|
|
192
240
|
end
|
193
241
|
app = @app || @app_default
|
194
242
|
help += "Usage: ./#{app} [commands] [options]\n"
|
195
|
-
help += @config.find{|x| x.name ==
|
243
|
+
help += @config.find{|x| x.name == @k.global}.help
|
196
244
|
help += "COMMANDS:\n"
|
197
|
-
@config.select{|x| x.name !=
|
245
|
+
@config.select{|x| x.name != @k.global}.each{|x| help += " #{x.name.ljust(@just)}#{x.desc}\n" }
|
198
246
|
help += "\nsee './#{app} COMMAND --help' for specific command help\n"
|
199
247
|
|
200
248
|
return help
|
@@ -202,94 +250,22 @@ class Commander
|
|
202
250
|
|
203
251
|
# Construct the command line parser and parse
|
204
252
|
def parse!
|
205
|
-
|
253
|
+
|
254
|
+
# Clear out the previous run every time, in case run more than once
|
255
|
+
@cmds = {}
|
206
256
|
|
207
257
|
# Set help if nothing was given
|
208
258
|
ARGV.clear and ARGV << '-h' if ARGV.empty?
|
209
|
-
|
210
|
-
# Process command options
|
211
|
-
#---------------------------------------------------------------------------
|
212
|
-
order_globals!
|
213
|
-
expand_chained_options!
|
214
|
-
loop {
|
215
|
-
break if ARGV.first.nil?
|
216
|
-
|
217
|
-
if !(cmd = @config.find{|x| x.name == ARGV.first}).nil?
|
218
|
-
@cmds[ARGV.shift.to_sym] = {} # Create command results entry
|
219
|
-
cmd_names.reject!{|x| x == cmd.name} # Remove command from possible commands
|
220
|
-
|
221
|
-
# Collect command options from args to compare against
|
222
|
-
opts = ARGV.take_while{|x| !cmd_names.include?(x) }
|
223
|
-
ARGV.shift(opts.size)
|
224
|
-
|
225
|
-
# Handle help upfront before anything else
|
226
|
-
if opts.any?{|x| m = match_named(x, cmd); m.hit? && m.sym == :help }
|
227
|
-
!puts(help) and exit if cmd.name == 'global'
|
228
|
-
!puts(cmd.help) and exit
|
229
|
-
end
|
230
259
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
named_opts = opts.select{|x| x.start_with?('-')}
|
239
|
-
cmd_named_opts.select{|x| x.required}.each{|x|
|
240
|
-
!puts("Error: required option #{x.key} not given!".colorize(:red)) && !puts(cmd.help) and
|
241
|
-
exit if !named_opts.find{|y| y.start_with?(x.short) || y.start_with?(x.long)}
|
242
|
-
}
|
243
|
-
|
244
|
-
# Process command options
|
245
|
-
pos = -1
|
246
|
-
loop {
|
247
|
-
break if opts.first.nil?
|
248
|
-
opt = opts.shift
|
249
|
-
cmd_opt = nil
|
250
|
-
value = nil
|
251
|
-
sym = nil
|
252
|
-
|
253
|
-
# Validate/set named options
|
254
|
-
# --------------------------------------------------------------------
|
255
|
-
# e.g. -s, --skip, --skip=VALUE
|
256
|
-
if (match = match_named(opt, cmd)).hit?
|
257
|
-
sym = match.sym
|
258
|
-
cmd_opt = match.opt
|
259
|
-
value = match.value
|
260
|
-
value = match.flag? || opts.shift if !value
|
261
|
-
|
262
|
-
# Validate/set positional options
|
263
|
-
# --------------------------------------------------------------------
|
264
|
-
else
|
265
|
-
pos += 1
|
266
|
-
value = opt
|
267
|
-
cmd_opt = cmd_pos_opts.shift
|
268
|
-
!puts("Error: invalid positional option '#{opt}'!".colorize(:red)) && !puts(cmd.help) and
|
269
|
-
exit if cmd_opt.nil? || cmd_names.include?(value)
|
270
|
-
sym = "#{cmd.name}#{pos}".to_sym
|
271
|
-
end
|
272
|
-
|
273
|
-
# Convert value to appropriate type and validate against allowed
|
274
|
-
# --------------------------------------------------------------------
|
275
|
-
value = convert_value(value, cmd, cmd_opt)
|
276
|
-
|
277
|
-
# Set option with value
|
278
|
-
# --------------------------------------------------------------------
|
279
|
-
!puts("Error: unknown named option '#{opt}' given!".colorize(:red)) && !puts(cmd.help) and exit if !sym
|
280
|
-
@cmds[cmd.name.to_sym][sym] = value
|
281
|
-
if cmd_opt.shared
|
282
|
-
sym = "shared#{pos}".to_sym if cmd_opt.key.nil?
|
283
|
-
@cmds[:shared] = {} if !@cmds.key?(:shared)
|
284
|
-
@cmds[:shared][sym] = value
|
285
|
-
end
|
286
|
-
}
|
287
|
-
end
|
288
|
-
}
|
260
|
+
# Parse commands recursively
|
261
|
+
move_globals_to_front!
|
262
|
+
expand_chained_options!
|
263
|
+
while (cmd = @config.find{|x| x.name == ARGV.first})
|
264
|
+
ARGV.shift && parse_commands(cmd, nil, @config.select{|x| x.name != cmd.name}, ARGV, @cmds)
|
265
|
+
end
|
289
266
|
|
290
|
-
# Ensure specials (global
|
267
|
+
# Ensure specials (global) are always set
|
291
268
|
@cmds[:global] = {} if !@cmds[:global]
|
292
|
-
@cmds[:shared] = {} if !@cmds[:shared]
|
293
269
|
|
294
270
|
# Ensure all options were consumed
|
295
271
|
Log.die("invalid options #{ARGV}") if ARGV.any?
|
@@ -307,15 +283,132 @@ class Commander
|
|
307
283
|
return !!sym
|
308
284
|
end
|
309
285
|
def flag?
|
310
|
-
return opt.type == FalseClass
|
286
|
+
return opt.type == FalseClass || opt.type == TrueClass
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Parse the given args recursively
|
291
|
+
# @param cmd [Command] command to work with
|
292
|
+
# @param parent [Command] command to work with
|
293
|
+
# @param others [Array] sibling cmds to cmd
|
294
|
+
# @param args [Array] array of arguments
|
295
|
+
# @param results [Hash] of cmd results
|
296
|
+
def parse_commands(cmd, parent, others, args, results)
|
297
|
+
results[cmd.to_sym] = {} # Create command results entry
|
298
|
+
cmd_names = others.map{|x| x.name} # Get other command names as markers
|
299
|
+
subcmds = cmd.nodes.select{|x| x.class == Command} # Get sub-commands for this command
|
300
|
+
|
301
|
+
# Collect all params until the next sibling command
|
302
|
+
#---------------------------------------------------------------------------
|
303
|
+
params = args.take_while{|x| !cmd_names.include?(x)}
|
304
|
+
args.shift(params.size)
|
305
|
+
|
306
|
+
# Strip off this command's preceeding options
|
307
|
+
opts = subcmds.any? ? params.take_while{|x| !subcmds.any?{|y| x == y.name}} : params
|
308
|
+
otherparams = params[opts.size..-1]
|
309
|
+
|
310
|
+
#---------------------------------------------------------------------------
|
311
|
+
# Handle sub-commands recursively first
|
312
|
+
#---------------------------------------------------------------------------
|
313
|
+
while subcmds.any? && (subcmd = subcmds.find{|x| x.name == otherparams.first})
|
314
|
+
otherparams.shift # Consume sub-cmd from opts
|
315
|
+
subcmds.reject!{|x| x.name == subcmd.name} # Drop sub-command from further use
|
316
|
+
parse_commands(subcmd, cmd, subcmds, otherparams, results[cmd.to_sym])
|
317
|
+
end
|
318
|
+
|
319
|
+
# Account for any left over options
|
320
|
+
otherparams.reverse.each{|x| opts.unshift(x)}
|
321
|
+
|
322
|
+
#---------------------------------------------------------------------------
|
323
|
+
# Base case: dealing with options for a given command.
|
324
|
+
# Only consume options for this command and bubble up unused to parent
|
325
|
+
#---------------------------------------------------------------------------
|
326
|
+
|
327
|
+
# Handle help upfront before anything else
|
328
|
+
#---------------------------------------------------------------------------
|
329
|
+
if opts.any?{|x| m = match_named(x, cmd); m.hit? && m.sym == :help }
|
330
|
+
!puts(help) and exit if cmd.name == @k.global
|
331
|
+
!puts(cmd.help) and exit
|
332
|
+
end
|
333
|
+
|
334
|
+
# Parse/consume named options first
|
335
|
+
#---------------------------------------------------------------------------
|
336
|
+
|
337
|
+
# Check that all required named options were given
|
338
|
+
cmd.nodes.select{|x| x.class == Option && !x.key.nil? && x.required}.each{|x|
|
339
|
+
!puts("Error: required option #{x.key} not given!".colorize(:red)) && !puts(cmd.help) and
|
340
|
+
exit if !match_named(x, opts).hit?
|
341
|
+
}
|
342
|
+
|
343
|
+
# Consume and set all named options
|
344
|
+
i = 0
|
345
|
+
while i < opts.size
|
346
|
+
if (match = match_named(opts[i], cmd)).hit?
|
347
|
+
value = match.flag? || match.value # Inline or Flag value
|
348
|
+
|
349
|
+
# Separate value
|
350
|
+
separate = false
|
351
|
+
if !value && i + 1 < opts.size
|
352
|
+
separate = true
|
353
|
+
value = opts[i + 1]
|
354
|
+
elsif !value
|
355
|
+
!puts("Error: named option '#{opts[i]}' value not found!".colorize(:red)) and
|
356
|
+
!puts(cmd.help) and exit
|
357
|
+
end
|
358
|
+
|
359
|
+
# Set result and consume options
|
360
|
+
results[cmd.to_sym][match.sym] = convert_value(value, cmd, match.opt)
|
361
|
+
opts.delete_at(i) # Consume option
|
362
|
+
opts.delete_at(i) if separate # Consume separate value
|
363
|
+
else
|
364
|
+
i += 1
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Parse/consume positional options next
|
369
|
+
#---------------------------------------------------------------------------
|
370
|
+
cmd_pos_opts = cmd.nodes.select{|x| x.class == Option && x.key.nil?}
|
371
|
+
|
372
|
+
# Check that all required positionals were given
|
373
|
+
!puts("Error: positional option required!".colorize(:red)) && !puts(cmd.help) and
|
374
|
+
exit if opts.select{|x| !x.start_with?('-')}.size < cmd_pos_opts.select{|x| x.required}.size
|
375
|
+
|
376
|
+
# Consume and set all positional options
|
377
|
+
i = 0
|
378
|
+
pos = -1
|
379
|
+
while i < opts.size && cmd_pos_opts.any?
|
380
|
+
if !opts[i].start_with?('-')
|
381
|
+
pos += 1
|
382
|
+
cmd_opt = cmd_pos_opts.shift
|
383
|
+
!puts("Error: invalid positional option '#{opts[i]}'!".colorize(:red)) and
|
384
|
+
!puts(cmd.help) and exit if cmd_opt.nil?
|
385
|
+
|
386
|
+
# Set result and consume options
|
387
|
+
results[cmd.to_sym]["#{cmd.to_sym}#{pos}".to_sym] = convert_value(opts[i], cmd, cmd_opt)
|
388
|
+
opts.delete_at(i) # Consume option
|
389
|
+
else
|
390
|
+
i += 1
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# Add any unconsumed options back to parent to ensure everything is accounted for
|
395
|
+
if parent
|
396
|
+
opts.reverse.each{|x| args.unshift(x)}
|
397
|
+
else
|
398
|
+
opts.each{|x|
|
399
|
+
!puts("Error: invalid positional option '#{x}'!".colorize(:red)) and
|
400
|
+
!puts(cmd.help) and exit if !x.start_with?('-')
|
401
|
+
!puts("Error: invalid named option '#{x}'!".colorize(:red)) and
|
402
|
+
!puts(cmd.help) and exit if x.start_with?('-')
|
403
|
+
}
|
311
404
|
end
|
312
405
|
end
|
313
406
|
|
314
407
|
# Parses the command line, moving all global options to the begining
|
315
408
|
# and inserting the global command
|
316
|
-
def
|
317
|
-
if !(global_cmd = @config.find{|x| x.name ==
|
318
|
-
ARGV.delete(
|
409
|
+
def move_globals_to_front!
|
410
|
+
if !(global_cmd = @config.find{|x| x.name == @k.global}).nil?
|
411
|
+
ARGV.delete(@k.global)
|
319
412
|
|
320
413
|
# Collect positional and named options from begining
|
321
414
|
globals = ARGV.take_while{|x| !@config.any?{|y| y.name == x}}
|
@@ -324,7 +417,7 @@ class Commander
|
|
324
417
|
# Collect named options throughout
|
325
418
|
i = -1
|
326
419
|
cmd = nil
|
327
|
-
while (i += 1) < ARGV.size
|
420
|
+
while (i += 1) < ARGV.size
|
328
421
|
|
329
422
|
# Set command and skip command and matching options
|
330
423
|
if !(_cmd = @config.find{|x| x.name == ARGV[i]}).nil?
|
@@ -342,7 +435,7 @@ class Commander
|
|
342
435
|
|
343
436
|
# Re-insert options in correct order at end with command
|
344
437
|
globals.reverse.each{|x| ARGV.unshift(x)}
|
345
|
-
ARGV.unshift(
|
438
|
+
ARGV.unshift(@k.global)
|
346
439
|
end
|
347
440
|
end
|
348
441
|
|
@@ -352,66 +445,118 @@ class Commander
|
|
352
445
|
def expand_chained_options!
|
353
446
|
args = ARGV[0..-1]
|
354
447
|
results = {}
|
355
|
-
|
356
|
-
|
448
|
+
cmd_order = []
|
449
|
+
cmd_names = @config.map{|x| x.name}
|
450
|
+
|
357
451
|
chained = []
|
358
|
-
while args.any?
|
452
|
+
while args.any?
|
359
453
|
if !(cmd = @config.find{|x| x.name == args.first}).nil?
|
360
|
-
|
361
|
-
|
362
|
-
|
454
|
+
cmd_order << args.shift # Maintain oder of given commands
|
455
|
+
results[cmd.name] = [] # Add the command to the results
|
456
|
+
cmd_names.reject!{|x| x == cmd.name} # Remove command from possible commands
|
363
457
|
|
364
458
|
# Collect command options from args to compare against
|
365
459
|
opts = args.take_while{|x| !cmd_names.include?(x)}
|
366
460
|
args.shift(opts.size)
|
367
461
|
|
368
462
|
# Globals are not to be considered for chaining
|
369
|
-
results[cmd.name].concat(opts) and next if cmd.name ==
|
463
|
+
results[cmd.name].concat(opts) and next if cmd.name == @k.global
|
370
464
|
|
371
|
-
# Chained case is when no options are given but
|
372
|
-
|
465
|
+
# Chained case is when no options are given but the command has options
|
466
|
+
cmd_options = cmd.nodes.select{|x| x.class == Option}
|
467
|
+
if opts.size == 0 && cmd_options.any?
|
373
468
|
chained << cmd
|
374
469
|
else
|
375
470
|
# Add cmd with options
|
376
471
|
results[cmd.name].concat(opts)
|
377
472
|
|
378
|
-
#
|
379
|
-
chained.each{|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
473
|
+
# Add applicable options to chained as well
|
474
|
+
chained.each{|other|
|
475
|
+
_opts = opts[0..-1]
|
476
|
+
named_results = []
|
477
|
+
positional_results = []
|
478
|
+
|
479
|
+
# Add all matching named options
|
480
|
+
#-------------------------------------------------------------------
|
481
|
+
i = 0
|
482
|
+
while i < _opts.size
|
483
|
+
if (match = match_named(_opts[i], other)).hit?
|
484
|
+
named_results << _opts[i];
|
485
|
+
_opts.delete_at(i)
|
486
|
+
|
487
|
+
# Get the next option to as the value was separate
|
488
|
+
if i < _opts.size && !(match.flag? || match.value)
|
489
|
+
named_results << _opts[i]
|
490
|
+
_opts.delete_at(i)
|
491
|
+
end
|
492
|
+
else
|
493
|
+
i += 1
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# Add all matching positional options
|
498
|
+
#-------------------------------------------------------------------
|
499
|
+
i = 0
|
500
|
+
other_positional = other.nodes.select{|x| x.class == Option && x.key.nil?}
|
501
|
+
while i < _opts.size
|
502
|
+
if !_opts[i].start_with?('-') && other_positional.any?
|
503
|
+
positional_results << _opts[i]
|
504
|
+
other_positional.shift
|
505
|
+
_opts.delete_at(i)
|
506
|
+
else
|
507
|
+
i += 1
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
positional_results.each{|x| results[other.name] << x}
|
512
|
+
named_results.each{|x| results[other.name] << x}
|
388
513
|
}
|
389
514
|
end
|
390
515
|
end
|
391
516
|
end
|
392
517
|
|
393
518
|
# Set results as new ARGV command line expression
|
394
|
-
ARGV.clear and
|
519
|
+
ARGV.clear and cmd_order.each{|x| ARGV << x; ARGV.concat(results[x]) }
|
395
520
|
end
|
396
521
|
|
397
522
|
# Match the given command line arg with a configured named option
|
398
|
-
#
|
399
|
-
# @param
|
523
|
+
# or match configured named option against a list of command line args
|
524
|
+
# @param arg [String/Option] the command line argument or configured Option
|
525
|
+
# @param other [Command/Array] configured command or command line args
|
400
526
|
# @return [OptionMatch]] struct with some helper functions
|
401
|
-
def match_named(
|
402
|
-
match = OptionMatch.new
|
403
|
-
|
404
|
-
|
405
|
-
if
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
match.
|
413
|
-
|
527
|
+
def match_named(arg, other)
|
528
|
+
match = OptionMatch.new
|
529
|
+
|
530
|
+
# Match command line arg against command options
|
531
|
+
if arg.class == String && other.class == Command
|
532
|
+
match.arg = arg
|
533
|
+
options = other.nodes.select{|x| x.class == Option && !x.key.nil? }
|
534
|
+
|
535
|
+
if arg.start_with?('-')
|
536
|
+
short = arg[@short_regex, 1]
|
537
|
+
long = arg[@long_regex, 1]
|
538
|
+
match.value = arg[@value_regex, 1]
|
539
|
+
|
540
|
+
# Set symbol converting dashes to underscores for named options
|
541
|
+
if (match.opt = options.find{|x| x.short == short || x.long == long})
|
542
|
+
match.sym = match.opt.to_sym
|
543
|
+
end
|
414
544
|
end
|
545
|
+
|
546
|
+
# Match command option against command line args
|
547
|
+
elsif arg.class == Option && other.class == Array
|
548
|
+
match.arg = arg.key
|
549
|
+
|
550
|
+
other.select{|x| x.start_with?('-')}.any?{|x|
|
551
|
+
short = x[@short_regex, 1]
|
552
|
+
long = x[@long_regex, 1]
|
553
|
+
value = x[@value_regex, 1]
|
554
|
+
if short == arg.short || long == arg.long
|
555
|
+
match.opt = arg
|
556
|
+
match.value = value
|
557
|
+
match.sym = match.opt.to_sym
|
558
|
+
end
|
559
|
+
}
|
415
560
|
end
|
416
561
|
|
417
562
|
return match
|
@@ -427,20 +572,20 @@ class Commander
|
|
427
572
|
if opt.type == String
|
428
573
|
if opt.allowed.any?
|
429
574
|
!puts("Error: invalid string value '#{value}'!".colorize(:red)) && !puts(cmd.help) and
|
430
|
-
exit if !opt.allowed.
|
575
|
+
exit if !opt.allowed.key?(value) && !opt.allowed.key?(value.to_sym)
|
431
576
|
end
|
432
577
|
elsif opt.type == Integer
|
433
578
|
value = value.to_i
|
434
579
|
if opt.allowed.any?
|
435
580
|
!puts("Error: invalid integer value '#{value}'!".colorize(:red)) && !puts(cmd.help) and
|
436
|
-
exit if !opt.allowed.
|
581
|
+
exit if !opt.allowed.key?(value)
|
437
582
|
end
|
438
583
|
elsif opt.type == Array
|
439
584
|
value = value.split(',')
|
440
585
|
if opt.allowed.any?
|
441
586
|
value.each{|x|
|
442
587
|
!puts("Error: invalid array value '#{x}'!".colorize(:red)) && !puts(cmd.help) and
|
443
|
-
exit if !opt.allowed.
|
588
|
+
exit if !opt.allowed.key?(x) && !opt.allowed.key?(x.to_sym)
|
444
589
|
}
|
445
590
|
end
|
446
591
|
end
|
@@ -450,37 +595,61 @@ class Commander
|
|
450
595
|
end
|
451
596
|
|
452
597
|
# Add a command to the command list
|
453
|
-
# @param
|
598
|
+
# @param name [String] name of the command
|
454
599
|
# @param desc [String] description of the command
|
455
|
-
# @param
|
600
|
+
# @param nodes [Array] list of command nodes (i.e. options or commands)
|
601
|
+
# @param hierarchy [Array] list of commands
|
456
602
|
# @return [Command] new command
|
457
|
-
def add_cmd(
|
458
|
-
|
603
|
+
def add_cmd(name, desc, nodes, hierarchy:[])
|
604
|
+
hierarchy << name
|
605
|
+
cmd = Command.new(name, desc)
|
606
|
+
subcmds = nodes.select{|x| x.class == Command}.sort{|x,y| x.name <=> y.name}
|
459
607
|
|
460
608
|
# Build help for command
|
609
|
+
#---------------------------------------------------------------------------
|
610
|
+
cmd.help = "#{desc}\n"
|
461
611
|
app = @app || @app_default
|
462
|
-
|
463
|
-
help += "\nUsage: ./#{app} #{
|
464
|
-
help = "#{banner}\n#{help}" if @app &&
|
612
|
+
cmd_prompt = subcmds.any? ? "[commands] " : ""
|
613
|
+
cmd.help += "\nUsage: ./#{app} #{hierarchy * ' '} #{cmd_prompt}[options]\n" if name != @k.global
|
614
|
+
cmd.help = "#{banner}\n#{cmd.help}" if @app && name != @k.global
|
465
615
|
|
466
|
-
# Add help
|
467
|
-
|
616
|
+
# Add help for each sub-command before options
|
617
|
+
cmd.help += "COMMANDS:\n" if subcmds.any?
|
618
|
+
subcmds.each{|x| cmd.help += " #{x.name.ljust(@just)}#{x.desc}\n" }
|
619
|
+
|
620
|
+
# Insert standard help option for command (re-using one from global, all identical)
|
621
|
+
nodes << @config.find{|x| x.name == @k.global}.nodes.find{|x| x.long == '--help'} if name != @k.global
|
468
622
|
|
469
623
|
# Add positional options first
|
470
|
-
sorted_options =
|
471
|
-
sorted_options +=
|
624
|
+
sorted_options = nodes.select{|x| x.class == Option && x.key.nil?}
|
625
|
+
sorted_options += nodes.select{|x| x.class == Option && !x.key.nil?}.sort{|x,y| x.key <=> y.key}
|
626
|
+
cmd.help += "OPTIONS:\n" if subcmds.any? && sorted_options.any?
|
472
627
|
positional_index = -1
|
473
|
-
sorted_options.each{|x|
|
628
|
+
sorted_options.each{|x|
|
474
629
|
required = x.required ? ", Required" : ""
|
475
|
-
allowed = x.allowed.empty? ? "" : " (#{x.allowed * ','})"
|
476
630
|
positional_index += 1 if x.key.nil?
|
477
|
-
key = x.key.nil? ? "#{
|
478
|
-
|
479
|
-
|
631
|
+
key = x.key.nil? ? "#{name}#{positional_index}" : x.key
|
632
|
+
if x.type == FalseClass || x.type == TrueClass
|
633
|
+
type = "Flag(#{x.type.to_s[/(.*)Class/, 1].downcase})"
|
634
|
+
elsif x.type == Array
|
635
|
+
type = "#{x.type}(String)"
|
636
|
+
else
|
637
|
+
type = x.type
|
638
|
+
end
|
639
|
+
cmd.help += " #{key.ljust(@just)}#{x.desc}: #{type}#{required}\n"
|
640
|
+
x.allowed.sort{|x,y| x <=> y}.each{|x| cmd.help += " #{''.ljust(@just)} #{x.first}: #{x.last}\n" }
|
480
641
|
}
|
481
642
|
|
482
|
-
#
|
483
|
-
|
643
|
+
# Add hint as to how to get specific sub command help
|
644
|
+
cmd.help += "\nsee './#{app} #{name} COMMAND --help' for specific command help\n" if subcmds.any?
|
645
|
+
|
646
|
+
# Configure help for each sub command
|
647
|
+
subcmds.each{|x| cmd.nodes << add_cmd(x.name, x.desc, x.nodes, hierarchy:hierarchy)}
|
648
|
+
|
649
|
+
# Add options after any sub-commands
|
650
|
+
cmd.nodes += sorted_options
|
651
|
+
|
652
|
+
return cmd
|
484
653
|
end
|
485
654
|
end
|
486
655
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.56
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Crummett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|