fuelcell 0.1.0
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 +15 -0
- data/.codeclimate.yml +12 -0
- data/.gitignore +13 -0
- data/.rspec +5 -0
- data/.travis.yml +15 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +5 -0
- data/bin/console +9 -0
- data/bin/example.rb +9 -0
- data/bin/setup +7 -0
- data/bin/test +20 -0
- data/bin/world.rb +6 -0
- data/fuelcell.gemspec +26 -0
- data/lib/fuelcell/action/arg_definition.rb +36 -0
- data/lib/fuelcell/action/arg_results.rb +57 -0
- data/lib/fuelcell/action/args_manager.rb +66 -0
- data/lib/fuelcell/action/callable.rb +54 -0
- data/lib/fuelcell/action/command.rb +72 -0
- data/lib/fuelcell/action/not_found.rb +55 -0
- data/lib/fuelcell/action/opt_definition.rb +79 -0
- data/lib/fuelcell/action/opt_results.rb +68 -0
- data/lib/fuelcell/action/opts_manager.rb +80 -0
- data/lib/fuelcell/action/root.rb +76 -0
- data/lib/fuelcell/action/subcommands.rb +81 -0
- data/lib/fuelcell/action.rb +11 -0
- data/lib/fuelcell/cli.rb +89 -0
- data/lib/fuelcell/help/base_formatter.rb +24 -0
- data/lib/fuelcell/help/builder.rb +71 -0
- data/lib/fuelcell/help/cmds_formatter.rb +57 -0
- data/lib/fuelcell/help/desc_formatter.rb +21 -0
- data/lib/fuelcell/help/opts_formatter.rb +62 -0
- data/lib/fuelcell/help/usage_formatter.rb +88 -0
- data/lib/fuelcell/help.rb +27 -0
- data/lib/fuelcell/parser/arg_handler.rb +31 -0
- data/lib/fuelcell/parser/base_handler.rb +133 -0
- data/lib/fuelcell/parser/cmd_args_strategy.rb +28 -0
- data/lib/fuelcell/parser/ignore_handler.rb +25 -0
- data/lib/fuelcell/parser/opt_handler.rb +58 -0
- data/lib/fuelcell/parser/opt_name_handler.rb +89 -0
- data/lib/fuelcell/parser/opt_value_equal_handler.rb +26 -0
- data/lib/fuelcell/parser/parsing_strategy.rb +80 -0
- data/lib/fuelcell/parser/short_opt_no_space_handler.rb +54 -0
- data/lib/fuelcell/parser.rb +4 -0
- data/lib/fuelcell/shell.rb +102 -0
- data/lib/fuelcell/version.rb +3 -0
- data/lib/fuelcell.rb +114 -0
- metadata +148 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Help
|
3
|
+
# Formats help text to be displayed on the command line. The formatter
|
4
|
+
# works with help text structured in a hash.
|
5
|
+
class UsageFormatter < BaseFormatter
|
6
|
+
|
7
|
+
def call(data)
|
8
|
+
usage = data[:usage] || {}
|
9
|
+
text = format_cmd(usage)
|
10
|
+
opts = format_opts(usage[:opts], text.size + 1)
|
11
|
+
text += ' ' + opts unless opts.empty?
|
12
|
+
|
13
|
+
"#{text}\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Format the options so that they wrap at the max width of the terminal
|
17
|
+
# and that they align after the command path.
|
18
|
+
#
|
19
|
+
# @param opts [Hash]
|
20
|
+
# @param align [Int]
|
21
|
+
# @return [String]
|
22
|
+
def format_opts(opts, align_width)
|
23
|
+
text = ''
|
24
|
+
line_width = align_width
|
25
|
+
opts.each do |opt|
|
26
|
+
text, line_width = append_opts(text, opt, line_width, align_width)
|
27
|
+
end
|
28
|
+
text.strip
|
29
|
+
end
|
30
|
+
|
31
|
+
# Format the label and the path which make the up
|
32
|
+
# the first line of usage text.
|
33
|
+
#
|
34
|
+
# @param data [Hash]
|
35
|
+
# @return [Array] formatted text, size of text
|
36
|
+
def format_cmd(data)
|
37
|
+
"#{data[:label]} #{data[:path]}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Append a single opt text to the usage line. Ensure wrapping
|
41
|
+
# occurs correctly & wrapped opt aligns with the start of the
|
42
|
+
# first opt.
|
43
|
+
#
|
44
|
+
# The align width is the amount of padding necessary in order to align
|
45
|
+
# all the opt text from the left.
|
46
|
+
#
|
47
|
+
# @param text [String] text of all currently appended options
|
48
|
+
# @param opt [String] text of the current option
|
49
|
+
# @param line_width [Int] width of the current line including padding
|
50
|
+
# @param align_width [Int] opts will align to this width after wrapping
|
51
|
+
def append_opts(text, opt, line_width, align_width)
|
52
|
+
str = opt_display(opt)
|
53
|
+
if (line_width + str.size) < max_width
|
54
|
+
return append_current_line(text, str, line_width)
|
55
|
+
end
|
56
|
+
|
57
|
+
append_nextline(text, str, align_width)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Used to append to the usage line when its shorted then the terminal
|
61
|
+
# window
|
62
|
+
#
|
63
|
+
# @param text [String] the current usage line
|
64
|
+
# @param str [String] the string to be appended
|
65
|
+
# @param line_width [Int] width of current line
|
66
|
+
# @return [Array] append text & size
|
67
|
+
def append_current_line(text, str, line_width)
|
68
|
+
line_width += str.size + 1
|
69
|
+
text << str + ' '
|
70
|
+
[text, line_width]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Used append a newline to the usage and wrap the text because it
|
74
|
+
# is long then the terminal width
|
75
|
+
#
|
76
|
+
def append_nextline(text, str, align_width)
|
77
|
+
text = text.strip + "\n"
|
78
|
+
text << (' ' * align_width) + str
|
79
|
+
[text, text.size]
|
80
|
+
end
|
81
|
+
|
82
|
+
def opt_display(data)
|
83
|
+
text = "#{short_opt(data)} #{long_opt(data)}".strip
|
84
|
+
"[#{text}]"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'fuelcell/help/builder'
|
2
|
+
require 'fuelcell/help/base_formatter'
|
3
|
+
require 'fuelcell/help/usage_formatter'
|
4
|
+
require 'fuelcell/help/opts_formatter'
|
5
|
+
require 'fuelcell/help/cmds_formatter'
|
6
|
+
require 'fuelcell/help/desc_formatter'
|
7
|
+
|
8
|
+
module Fuelcell
|
9
|
+
module Help
|
10
|
+
class << self
|
11
|
+
def generate(root, args = [], width = 80)
|
12
|
+
data = Builder.new.call(root, args)
|
13
|
+
text = UsageFormatter.new(width: width).call(data)
|
14
|
+
|
15
|
+
line = DescFormatter.new(width: width).call(data)
|
16
|
+
text << "\n" + line unless line.empty?
|
17
|
+
|
18
|
+
line = OptsFormatter.new(width: width).call(data)
|
19
|
+
text << "\n" + line unless line.empty?
|
20
|
+
|
21
|
+
line = CmdsFormatter.new(width: width).call(data)
|
22
|
+
text << "\n" + line unless line.empty?
|
23
|
+
text
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
# Parse args based on command definitions & cast the value into the correct
|
4
|
+
# type. This will leave us with the args array (its values modified by the
|
5
|
+
# command definitions) and an args hash with its keys derived from the
|
6
|
+
# definition. Boths theses lists are needed to create the ArgResult which
|
7
|
+
# is returned
|
8
|
+
class ArgHandler < BaseHandler
|
9
|
+
SKIP = '__FUELCELL_SKIP_ARG__'
|
10
|
+
# @param cmd [Fuelcell::Command]
|
11
|
+
# @param args [Array] raw args from ARGV
|
12
|
+
# @return [Fuelcell::Action::ArgsResults]
|
13
|
+
def call(cmd, args)
|
14
|
+
list = {}
|
15
|
+
manager = cmd.args
|
16
|
+
manager.each_with_index do |arg, index|
|
17
|
+
value = args.fetch(index) {
|
18
|
+
fail "argument at #{index} is required" if arg.required?
|
19
|
+
arg.default? ? arg.default : SKIP
|
20
|
+
}
|
21
|
+
next if value == SKIP
|
22
|
+
args[index] = cast(arg.type, value)
|
23
|
+
# assign a pointer to the casted value
|
24
|
+
list[arg.name] = index
|
25
|
+
end
|
26
|
+
|
27
|
+
Fuelcell::Action::ArgResults.new(args, list)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
# Parsing the command line involves a command object, raw args array and
|
4
|
+
# the final options hash. Handlers use the command object to determine
|
5
|
+
# specifications on options and args, they will add and remove raw args
|
6
|
+
# and add to the options hash. This class provides helper methods used
|
7
|
+
# to encapsulate the command tasks needed by every handler
|
8
|
+
class BaseHandler
|
9
|
+
|
10
|
+
# Anything that does not start with an "-" is considered an arg
|
11
|
+
# nil is not considered an arg or an opt, since all elements in
|
12
|
+
# ARGV are strings
|
13
|
+
#
|
14
|
+
# @param text [String] represents an element from ARGV
|
15
|
+
# @return [Boolean]
|
16
|
+
def arg?(text)
|
17
|
+
return false if text.nil?
|
18
|
+
!text.to_s.start_with?('-')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Search for an opt definition in the given command object
|
22
|
+
#
|
23
|
+
# @param cmd [Fuelcell::Command] command found on the cli
|
24
|
+
# @param name [String] name of the object
|
25
|
+
# @return [Fuelcell::OptDefinition]
|
26
|
+
def find_opt(cmd, name)
|
27
|
+
opt = cmd.find_opt(name)
|
28
|
+
fail "option #{name} is not registered" unless opt
|
29
|
+
opt
|
30
|
+
end
|
31
|
+
|
32
|
+
# Allows handlers to match the first raw arg against a
|
33
|
+
# regex or someother condition and if it fails that arg
|
34
|
+
# will be put back into the raw args otherwise the handler
|
35
|
+
# will process it
|
36
|
+
#
|
37
|
+
# params arg [String]
|
38
|
+
# returns [Boolean, String]
|
39
|
+
def take_first_arg(args)
|
40
|
+
arg = args.shift
|
41
|
+
unless yield arg
|
42
|
+
args.unshift arg
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
arg
|
46
|
+
end
|
47
|
+
|
48
|
+
# Assigns opt name and value into the opts hash. It provides a common
|
49
|
+
# place for validation checks and casting.
|
50
|
+
#
|
51
|
+
# @param opts [Hash] holds processed opts
|
52
|
+
# @param opt [Fuelcell::OptDefinition]
|
53
|
+
# @param value [String] value found in raw_args
|
54
|
+
# @param raw_arg [String] used for error msg, the actual opt string
|
55
|
+
# @return the casted value
|
56
|
+
def assign_opt_value(opts, opt, value, raw_arg)
|
57
|
+
fail "#{raw_arg} is a flag and can not accept values" if opt.flag?
|
58
|
+
|
59
|
+
opts[opt.name] = cast(opt.type, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def cast_numeric(value)
|
63
|
+
unless value =~ /[-+]?\d*\.\d+|\d+/
|
64
|
+
fail ArgumentError, "expecting a numeric type, '#{value}' is invalid"
|
65
|
+
end
|
66
|
+
value.index('.') ? value.to_f : value.to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
def cast_bool(value)
|
70
|
+
case value.to_s.downcase
|
71
|
+
when 'true', 'yes', '1' then true
|
72
|
+
when 'false', 'no', '0' then false
|
73
|
+
else
|
74
|
+
fail ArgumentError, "expecting bool value like " +
|
75
|
+
"(true,false,yes,no,1,0), #{value} is invalid"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Collect all values in raw args upto the first option and put then
|
80
|
+
# into an array
|
81
|
+
#
|
82
|
+
# @params value [String] the first value of the array
|
83
|
+
# @params raw_args [Array] raw args used to collect remaining items
|
84
|
+
# @return [Array]
|
85
|
+
def cast_array(value)
|
86
|
+
value.to_s.split(',')
|
87
|
+
end
|
88
|
+
|
89
|
+
# A hash key value pair is assumed to be in the form key:value. Collect
|
90
|
+
# all args that are not opts and process them.
|
91
|
+
#
|
92
|
+
# @param value [String] the first key value pair
|
93
|
+
# @param raw_args [Array] raw args to collect remaining hash values
|
94
|
+
# @return [Hash]
|
95
|
+
def cast_hash(value)
|
96
|
+
list = cast_array(value)
|
97
|
+
result = {}
|
98
|
+
list.each do |item|
|
99
|
+
key, value = item.split(':', 2)
|
100
|
+
result[key] = value
|
101
|
+
end
|
102
|
+
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
# Delegate the cast based on type
|
107
|
+
#
|
108
|
+
# @param type [Symbol]
|
109
|
+
# @param value [String]
|
110
|
+
# @param raw_args [Array]
|
111
|
+
# @return casted value
|
112
|
+
def cast(type, value)
|
113
|
+
case type
|
114
|
+
when :numeric then cast_numeric(value)
|
115
|
+
when :bool then cast_bool(value)
|
116
|
+
when :hash then cast_hash(value)
|
117
|
+
when :array then cast_array(value)
|
118
|
+
else
|
119
|
+
value.to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# A flag has a value of true when its found
|
124
|
+
#
|
125
|
+
# @param opts [Hash] processed opts
|
126
|
+
# @param opt [Fuelcell::OptDefinition]
|
127
|
+
# @return [Boolean]
|
128
|
+
def found_opt_flag(opts, opt)
|
129
|
+
opts[opt.name] = true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
class CmdArgsStrategy
|
4
|
+
|
5
|
+
# Extract arguments that form the command to be executed.
|
6
|
+
#
|
7
|
+
# This will collect all arguments up to the first option or the ignore
|
8
|
+
# symbol --. It separates and returns the command args as an array of
|
9
|
+
# strings that form a heirarchal route to the command. These command args
|
10
|
+
# are removed from the raw arg list
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# @param [Array] raw args from ARGV
|
14
|
+
# @return <Array>
|
15
|
+
def call(args)
|
16
|
+
cmd_args = []
|
17
|
+
while item = args.shift
|
18
|
+
if item.start_with?('-')
|
19
|
+
args.unshift(item)
|
20
|
+
break
|
21
|
+
end
|
22
|
+
cmd_args << item
|
23
|
+
end
|
24
|
+
cmd_args
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
# Removes any args after the string '--' is found in the raw_args. It also
|
4
|
+
# removes the ignore string itself from the raw args.
|
5
|
+
#
|
6
|
+
# @param raw_args [Array] list of args usually from ARGV
|
7
|
+
# @return [Array]
|
8
|
+
class IgnoreHandler
|
9
|
+
def call(raw_args)
|
10
|
+
unless raw_args.is_a?(Array)
|
11
|
+
fail ArgumentError, 'raw args must be an array'
|
12
|
+
end
|
13
|
+
|
14
|
+
ignores = []
|
15
|
+
index = raw_args.index('--')
|
16
|
+
return ignores unless index
|
17
|
+
|
18
|
+
ignores = raw_args.slice!(index, raw_args.size)
|
19
|
+
# remove the ignore symbol --
|
20
|
+
ignores.shift
|
21
|
+
ignores
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
# Process options that are flags or use another arg as their value. This
|
4
|
+
# handles both short and long options. Flags are assigned a value of true
|
5
|
+
# while all other values are casted during value assignment
|
6
|
+
class OptHandler < BaseHandler
|
7
|
+
# @param cmd [Fuelcell::Command]
|
8
|
+
# @param args [Array] raw args from ARGV
|
9
|
+
# @param opts [Hash] stores the found opts
|
10
|
+
# @return [Boolean]
|
11
|
+
def call(cmd, args, opts)
|
12
|
+
arg = take_first_arg(args) do |text|
|
13
|
+
!(text =~ /^(--\w+(?:-\w+)*|-[a-zA-Z])$/).nil?
|
14
|
+
end
|
15
|
+
return false unless arg
|
16
|
+
|
17
|
+
opt = find_opt(cmd, arg)
|
18
|
+
return true if handled_flag?(opts, opt)
|
19
|
+
|
20
|
+
# We know we are not a flag, so we are expecting a value.
|
21
|
+
# We also know there are no other values available so for now
|
22
|
+
# this is considered a failure condition
|
23
|
+
fail_value_required(opt) if args.empty?
|
24
|
+
|
25
|
+
handle_value(opts, opt, arg, args)
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def handle_value(opts, opt, arg, args)
|
32
|
+
value = args.shift
|
33
|
+
unless arg?(value)
|
34
|
+
if opt.default?
|
35
|
+
value = opt.default
|
36
|
+
else
|
37
|
+
fail_value_required(opt)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
assign_opt_value(opts, opt, value, arg)
|
42
|
+
end
|
43
|
+
|
44
|
+
def handled_flag?(opts, opt)
|
45
|
+
return false unless opt.flag?
|
46
|
+
found_opt_flag(opts, opt)
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def fail_value_required(opt)
|
51
|
+
return if opt.callable? || opt.cmd_path?
|
52
|
+
|
53
|
+
cli_opts = opt.cli_labels.join(' or ')
|
54
|
+
fail "value not found for #{cli_opts} when value is required"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
class OptNameHandler
|
4
|
+
# Parses the option defintiion name according to a set of rules that
|
5
|
+
# allows a short hand to be used to give the option name, its cli
|
6
|
+
# long & short names. The names are separated by |
|
7
|
+
#
|
8
|
+
# specify all 3: <opt name>|<cli long>|<cli short>
|
9
|
+
# when manually specifying all three the opt name is
|
10
|
+
# always first
|
11
|
+
# ex) version-opt|version|v
|
12
|
+
#
|
13
|
+
# specify 2: <cli long>|<cli short>
|
14
|
+
# <cli short>|<cli long>
|
15
|
+
# <opt name>|<cli long>
|
16
|
+
# when specifying 2 the opt name & cli long will be the same
|
17
|
+
# if one of the given names is a cli short
|
18
|
+
# ex) version|v
|
19
|
+
# ex) v|version
|
20
|
+
# ex) version-opt|version - only opt name and cli long are set
|
21
|
+
#
|
22
|
+
# specify 1: <cli-long>
|
23
|
+
# <cli-short>
|
24
|
+
# ex) v - cli short and opt name will be the same, no
|
25
|
+
# cli longs will be set
|
26
|
+
# ex) version - cli long and opt name will be the same, no
|
27
|
+
# cli short will be set
|
28
|
+
#
|
29
|
+
#
|
30
|
+
# @param name [String]
|
31
|
+
def call(text)
|
32
|
+
text = text.to_s
|
33
|
+
fail ArgumentError, 'option name can not be empty' if text.empty?
|
34
|
+
|
35
|
+
parts = text.split('|')
|
36
|
+
method_name = "manually_assign_#{parts.size}"
|
37
|
+
name, long, short = send(method_name, parts)
|
38
|
+
|
39
|
+
if !short.nil? && short.size > 1
|
40
|
+
fail ArgumentError, "option short name (#{short}) can only be 1 char"
|
41
|
+
end
|
42
|
+
|
43
|
+
[name, long, short]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def manually_assign_3(names)
|
49
|
+
if names[0].size == 1
|
50
|
+
fail ArgumentError,
|
51
|
+
'short name can not be first when setting all 3 names'
|
52
|
+
end
|
53
|
+
|
54
|
+
if names[1].size > 1
|
55
|
+
name, long, short = names[0..2]
|
56
|
+
else
|
57
|
+
name, short, long = names[0..2]
|
58
|
+
end
|
59
|
+
[name, long, short]
|
60
|
+
end
|
61
|
+
|
62
|
+
def manually_assign_2(names)
|
63
|
+
name, long = names[0..1]
|
64
|
+
short = nil
|
65
|
+
if names.last.size == 1
|
66
|
+
long, short = names[0..1]
|
67
|
+
name = long
|
68
|
+
elsif names.first.size == 1
|
69
|
+
short, long = names[0..1]
|
70
|
+
name = long
|
71
|
+
end
|
72
|
+
[name, long, short]
|
73
|
+
end
|
74
|
+
|
75
|
+
def manually_assign_1(names)
|
76
|
+
name = names.first
|
77
|
+
if name.size == 1
|
78
|
+
short = name
|
79
|
+
long = nil
|
80
|
+
else
|
81
|
+
long = name
|
82
|
+
short = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
[name, long, short]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
class OptValueEqualHandler < BaseHandler
|
4
|
+
|
5
|
+
# Handle options both long and short that express their value using an
|
6
|
+
# equal sign like --foo=bar or -f=bar
|
7
|
+
#
|
8
|
+
# @param cmd [Fuelcell::Command]
|
9
|
+
# @param args [Array] raw args from ARGV
|
10
|
+
# @param opts [Hash] stores the found opts
|
11
|
+
# @return [Boolean]
|
12
|
+
def call(cmd, args, opts)
|
13
|
+
return false unless take_first_arg(args) do |arg|
|
14
|
+
!!(arg =~ /^(--\w+(?:-\w+)*|-[a-zA-Z])=(.*)$/)
|
15
|
+
end
|
16
|
+
|
17
|
+
name = $1
|
18
|
+
value = $2
|
19
|
+
|
20
|
+
opt = find_opt(cmd, name)
|
21
|
+
assign_opt_value(opts, opt, value, name)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative 'ignore_handler'
|
2
|
+
require_relative 'opt_value_equal_handler'
|
3
|
+
require_relative 'short_opt_no_space_handler'
|
4
|
+
require_relative 'opt_handler'
|
5
|
+
require_relative 'arg_handler'
|
6
|
+
|
7
|
+
module Fuelcell
|
8
|
+
module Parser
|
9
|
+
class ParsingStrategy < BaseHandler
|
10
|
+
attr_reader :ignore_handler, :opt_handlers, :arg_handler
|
11
|
+
|
12
|
+
# Arrange all the handlers in the correct order for this
|
13
|
+
# strategy.The order is as follows:
|
14
|
+
# 1. short clusters
|
15
|
+
# 2. opts using equals
|
16
|
+
# 3. short opts that have no space between them and their value
|
17
|
+
# 4. all other option conditions
|
18
|
+
# 5. check that all required options have been processed.
|
19
|
+
#
|
20
|
+
# The check for required options is designed to be handled last
|
21
|
+
def initialize
|
22
|
+
@ignore_handler = IgnoreHandler.new
|
23
|
+
@opt_handlers = [
|
24
|
+
ShortOptNoSpaceHandler.new,
|
25
|
+
OptValueEqualHandler.new,
|
26
|
+
OptHandler.new
|
27
|
+
]
|
28
|
+
|
29
|
+
@arg_handler = ArgHandler.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Run all handlers in order, in a continuous loop until all raw args
|
33
|
+
# are empty. Handlers implement the call method as a strategy pattern
|
34
|
+
# so they all have the same signature. During the loop and after all
|
35
|
+
# the handlers have run, we check the first arg, if it's not an opt
|
36
|
+
# it means it's a arg so we move it to the args array. This
|
37
|
+
# process continues until there are no other args. Required options
|
38
|
+
# are not inforced for the help command
|
39
|
+
#
|
40
|
+
# @param cmd [Fuelcell::Command]
|
41
|
+
# @param raw_args [Array] raw args from ARGV
|
42
|
+
# @param opts [Hash] stores processed options
|
43
|
+
# @return [Array] the process options and processed args
|
44
|
+
def call(cmd, raw_args, opts = Fuelcell::Action::OptResults.new)
|
45
|
+
ignores = ignore_handler.call(raw_args)
|
46
|
+
args = run_option_handlers(cmd, raw_args, opts)
|
47
|
+
args = arg_handler.call(cmd, args)
|
48
|
+
unless cmd.name == 'help'
|
49
|
+
cmd.missing_opts(opts.keys) do |missing|
|
50
|
+
fail "option [#{missing.first.cli_names}] is required"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
format_return(cmd, opts, args, ignores)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def run_option_handlers(cmd, raw_args, opts)
|
60
|
+
args = []
|
61
|
+
until raw_args.empty?
|
62
|
+
opt_handlers.each do |handler|
|
63
|
+
break if handler.call(cmd, raw_args, opts)
|
64
|
+
end
|
65
|
+
args << raw_args.shift if arg?(raw_args.first)
|
66
|
+
end
|
67
|
+
args
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_return(cmd, opts, args, ignores)
|
71
|
+
{
|
72
|
+
cmd: cmd,
|
73
|
+
opts: opts,
|
74
|
+
args: args,
|
75
|
+
ignores: ignores
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Fuelcell
|
2
|
+
module Parser
|
3
|
+
# Handles short options that express their value without using a space like
|
4
|
+
# -uroot where the short opt is -u and value is root
|
5
|
+
class ShortOptNoSpaceHandler < BaseHandler
|
6
|
+
# @param cmd [Fuelcell::Command]
|
7
|
+
# @param args [Array] raw args from ARGV
|
8
|
+
# @param opts [Hash] stores the found opts
|
9
|
+
# @return [Boolean]
|
10
|
+
def call(cmd, args, opts)
|
11
|
+
arg = take_first_arg(args) do |text|
|
12
|
+
!(text =~ /^-([a-zA-Z]{2,})$/).nil?
|
13
|
+
end
|
14
|
+
return false unless arg
|
15
|
+
|
16
|
+
return true if handle_no_space_value(cmd, opts, args, arg)
|
17
|
+
|
18
|
+
arg[0] = '' # remove the dash
|
19
|
+
handle_cluster(cmd, opts, arg)
|
20
|
+
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# we need to determine if this is a short opt with a value
|
27
|
+
# smushed into it or a cluster of opts
|
28
|
+
def handle_no_space_value(cmd, opts, args, arg)
|
29
|
+
name = "-#{arg[1]}"
|
30
|
+
value = arg.slice(2, arg.size)
|
31
|
+
opt = cmd.find_opt(name)
|
32
|
+
|
33
|
+
fail "#{name} is not a registered option" unless opt
|
34
|
+
|
35
|
+
unless opt.flag?
|
36
|
+
assign_opt_value(opts, opt, value, name)
|
37
|
+
return true
|
38
|
+
end
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_cluster(cmd, opts, arg)
|
43
|
+
cluster = arg.split('')
|
44
|
+
cluster.each do |short|
|
45
|
+
opt = cmd.find_opt("-#{short}")
|
46
|
+
unless opt.flag?
|
47
|
+
fail "-#{short} can not live in a cluster, it must be a flag"
|
48
|
+
end
|
49
|
+
found_opt_flag(opts, opt)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|