clin 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/examples/dispatcher.rb +22 -50
- data/examples/list_option.rb +27 -0
- data/examples/nested_dispatcher.rb +28 -27
- data/examples/simple.rb +1 -1
- data/examples/test.rb +8 -1
- data/lib/clin/argument.rb +28 -11
- data/lib/clin/command.rb +25 -72
- data/lib/clin/command_dispatcher.rb +2 -1
- data/lib/clin/command_options_mixin.rb +33 -3
- data/lib/clin/command_parser.rb +115 -0
- data/lib/clin/common/help_options.rb +0 -1
- data/lib/clin/errors.rb +7 -2
- data/lib/clin/general_option.rb +5 -5
- data/lib/clin/option.rb +70 -16
- data/lib/clin/option_list.rb +25 -0
- data/lib/clin/version.rb +1 -1
- data/lib/clin.rb +14 -11
- data/spec/clin/command_parser_spec.rb +165 -0
- data/spec/clin/command_spec.rb +112 -0
- data/spec/clin/option_list_spec.rb +32 -0
- data/spec/{cli → clin}/option_spec.rb +10 -2
- data/spec/examples/list_option_spec.rb +20 -0
- data/spec/examples/nested_dispatcher_spec.rb +28 -0
- metadata +26 -14
- data/spec/cli/command_spec.rb +0 -225
- /data/spec/{cli → clin}/argument_spec.rb +0 -0
- /data/spec/{cli → clin}/command_dispacher_spec.rb +0 -0
- /data/spec/{cli → clin}/command_options_mixin_spec.rb +0 -0
- /data/spec/{cli → clin}/common/help_options_spec.rb +0 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Command parser
|
4
|
+
class Clin::CommandParser
|
5
|
+
# Create the command parser
|
6
|
+
# @param command_cls [Class<Clin::Command>] Command that must be matched
|
7
|
+
# @param argv [Array<String>] List of CL arguments
|
8
|
+
# @param fallback_help [Boolean] If the parse should raise an HelpError or the real error.
|
9
|
+
def initialize(command_cls, argv = ARGV, fallback_help: true)
|
10
|
+
@command = command_cls
|
11
|
+
argv = Shellwords.split(argv) if argv.is_a? String
|
12
|
+
@argv = argv
|
13
|
+
@fallback_help = fallback_help
|
14
|
+
end
|
15
|
+
|
16
|
+
# Parse the command line.
|
17
|
+
def parse
|
18
|
+
argv = @argv.clone
|
19
|
+
error = nil
|
20
|
+
options = {}
|
21
|
+
begin
|
22
|
+
options.merge! parse_options(argv)
|
23
|
+
rescue Clin::OptionError => e
|
24
|
+
error = e
|
25
|
+
end
|
26
|
+
begin
|
27
|
+
options.merge! parse_arguments(argv)
|
28
|
+
rescue Clin::ArgumentError => e
|
29
|
+
raise e unless @fallback_help
|
30
|
+
error = e
|
31
|
+
end
|
32
|
+
|
33
|
+
return redispatch(options) if @command.redispatch?
|
34
|
+
obj = @command.new(options)
|
35
|
+
handle_error(error)
|
36
|
+
obj
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parse the options in the argv.
|
40
|
+
# @return [Array] the list of argv that are not options(positional arguments)
|
41
|
+
def parse_options(argv)
|
42
|
+
out = {}
|
43
|
+
parser = @command.option_parser(out)
|
44
|
+
skipped = skipped_options
|
45
|
+
argv.reject! { |x| skipped.include?(x) }
|
46
|
+
begin
|
47
|
+
parser.parse!(argv)
|
48
|
+
rescue OptionParser::InvalidOption => e
|
49
|
+
raise Clin::OptionError, e.to_s
|
50
|
+
end
|
51
|
+
out[:skipped_options] = skipped if @command.skip_options?
|
52
|
+
out
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get the options that have been skipped by options_first!
|
56
|
+
def skipped_options
|
57
|
+
return [] unless @command.skip_options?
|
58
|
+
argv = @argv.dup
|
59
|
+
skipped = []
|
60
|
+
parser = @command.option_parser
|
61
|
+
loop do
|
62
|
+
begin
|
63
|
+
parser.parse!(argv)
|
64
|
+
break
|
65
|
+
rescue OptionParser::InvalidOption => e
|
66
|
+
skipped << e.to_s.sub(/invalid option:\s+/, '')
|
67
|
+
next if argv.empty? || argv.first.start_with?('-')
|
68
|
+
skipped << argv.shift
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
skipped
|
73
|
+
end
|
74
|
+
|
75
|
+
# Parse the argument. The options must have been strip out first.
|
76
|
+
def parse_arguments(argv)
|
77
|
+
out = {}
|
78
|
+
@command.args.each do |arg|
|
79
|
+
value, argv = arg.parse(argv)
|
80
|
+
out[arg.name.to_sym] = value
|
81
|
+
end
|
82
|
+
out.delete_if { |_, v| v.nil? }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Method called after the argument have been parsed and before creating the command
|
86
|
+
# @param params [Array<String>] Parsed params from the command line.
|
87
|
+
def redispatch(params)
|
88
|
+
commands = @command._redispatch_args.last
|
89
|
+
commands ||= @command.default_commands
|
90
|
+
dispatcher = Clin::CommandDispatcher.new(commands)
|
91
|
+
begin
|
92
|
+
dispatcher.parse(redispatch_arguments(params))
|
93
|
+
rescue Clin::HelpError
|
94
|
+
raise Clin::HelpError, @command.option_parser
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Compute the list of argument to pass to the CommandDispatcher
|
99
|
+
# @param params [Hash] Options and Arguments of the CL
|
100
|
+
def redispatch_arguments(params)
|
101
|
+
args, prefix = @command._redispatch_args
|
102
|
+
args = args.map { |x| params[x] }.flatten.compact
|
103
|
+
args = prefix.split + args unless prefix.nil?
|
104
|
+
args += params[:skipped_options] if @command.skip_options?
|
105
|
+
args
|
106
|
+
end
|
107
|
+
|
108
|
+
# Guard that check if there was an error and fail HelpError if there was
|
109
|
+
# @raise [Clin::HelpError]
|
110
|
+
def handle_error(error)
|
111
|
+
return unless error
|
112
|
+
fail Clin::HelpError, @command.option_parser if @fallback_help
|
113
|
+
fail error
|
114
|
+
end
|
115
|
+
end
|
data/lib/clin/errors.rb
CHANGED
@@ -14,6 +14,9 @@ module Clin
|
|
14
14
|
|
15
15
|
# Error when a fixed argument is not matched
|
16
16
|
class FixedArgumentError < ArgumentError
|
17
|
+
# Create a new FixedArgumentError
|
18
|
+
# @param argument [String] Name of the fixed argument
|
19
|
+
# @param got [String] What argument was in place of the fixed argument
|
17
20
|
def initialize(argument = '', got = '')
|
18
21
|
super("Expecting '#{argument}' but got '#{got}'")
|
19
22
|
end
|
@@ -21,8 +24,10 @@ module Clin
|
|
21
24
|
|
22
25
|
# Error when a command is missing an argument
|
23
26
|
class MissingArgumentError < ArgumentError
|
24
|
-
|
25
|
-
|
27
|
+
# Create a new MissingArgumentError
|
28
|
+
# @param argument [String] Name of the missing argument
|
29
|
+
def initialize(argument = '')
|
30
|
+
super("Missing argument #{argument}")
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
data/lib/clin/general_option.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'clin'
|
2
2
|
|
3
|
+
# Parent class for reusable options across commands
|
3
4
|
class Clin::GeneralOption < Clin::CommandOptionsMixin
|
4
|
-
|
5
|
-
def initialize(config = {})
|
6
|
-
|
5
|
+
def initialize(_config = {})
|
7
6
|
end
|
8
7
|
|
9
8
|
# It get the params the general options needs and do whatever the option is suppose to do with it.
|
10
|
-
# Method called in the initialize of the command.
|
9
|
+
# Method called in the initialize of the command.
|
10
|
+
# This allow general options to be extracted when parsing a command line
|
11
11
|
# as well as calling the command directly in the code
|
12
12
|
# @param _params [Hash] Params got in the command
|
13
13
|
def execute(_params)
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
data/lib/clin/option.rb
CHANGED
@@ -1,24 +1,40 @@
|
|
1
1
|
require 'clin'
|
2
2
|
|
3
3
|
# Option container.
|
4
|
+
# Prefer the `.option`, `.flag_option`,... class methods than `.add_option Option.new(...)`
|
4
5
|
class Clin::Option
|
5
|
-
attr_accessor :name, :description, :optional_argument, :block
|
6
|
+
attr_accessor :name, :description, :optional_argument, :block, :type, :default
|
6
7
|
attr_reader :short, :long, :argument
|
7
8
|
|
8
|
-
|
9
|
+
# Create a new option.
|
10
|
+
# @param name [String] Option name.
|
11
|
+
# @param description [String] Option Description.
|
12
|
+
# @param short [String|Boolean]
|
13
|
+
# @param long [String|Boolean]
|
14
|
+
# @param argument [String|Boolean]
|
15
|
+
# @param argument_optional [Boolean]
|
16
|
+
# @param type [Class]
|
17
|
+
# @param default [Class] If the option is not specified set the default value.
|
18
|
+
# If default is nil the key will not be added to the params
|
19
|
+
# @param block [Block]
|
20
|
+
def initialize(name, description, short: nil, long: nil,
|
21
|
+
argument: nil, argument_optional: false, type: nil, default: nil, &block)
|
9
22
|
@name = name
|
10
23
|
@description = description
|
11
24
|
@short = short
|
12
25
|
@long = long
|
13
|
-
@optional_argument =
|
26
|
+
@optional_argument = argument_optional
|
14
27
|
@argument = argument
|
28
|
+
@type = type
|
15
29
|
@block = block
|
30
|
+
@default = default
|
16
31
|
end
|
17
32
|
|
18
33
|
# Register the option to the Option Parser
|
19
34
|
# @param opts [OptionParser]
|
20
35
|
# @param out [Hash] Out options mapping
|
21
36
|
def register(opts, out)
|
37
|
+
load_default(out)
|
22
38
|
if @block.nil?
|
23
39
|
opts.on(*option_parser_arguments) do |value|
|
24
40
|
on(value, out)
|
@@ -30,14 +46,30 @@ class Clin::Option
|
|
30
46
|
end
|
31
47
|
end
|
32
48
|
|
49
|
+
# Default option short name.
|
50
|
+
# ```
|
51
|
+
# :verbose => '-v'
|
52
|
+
# :help => '-h'
|
53
|
+
# :Require => '-r'
|
54
|
+
# ```
|
33
55
|
def default_short
|
34
56
|
"-#{name[0].downcase}"
|
35
57
|
end
|
36
58
|
|
59
|
+
# Default option long name.
|
60
|
+
# ```
|
61
|
+
# :verbose => '--verbose'
|
62
|
+
# :Require => '--require'
|
63
|
+
# :add_stuff => '--add-stuff'
|
64
|
+
# ```
|
37
65
|
def default_long
|
38
|
-
"--#{name.downcase}"
|
66
|
+
"--#{name.to_s.downcase.dasherize}"
|
39
67
|
end
|
40
68
|
|
69
|
+
# Default argument
|
70
|
+
# ```
|
71
|
+
# :Require => 'REQUIRE'
|
72
|
+
# ```
|
41
73
|
def default_argument
|
42
74
|
name.to_s.upcase
|
43
75
|
end
|
@@ -47,7 +79,7 @@ class Clin::Option
|
|
47
79
|
# If @short is false it will return nil
|
48
80
|
# @return [String]
|
49
81
|
def short
|
50
|
-
return nil if @short
|
82
|
+
return nil if @short.eql? false
|
51
83
|
@short ||= default_short
|
52
84
|
end
|
53
85
|
|
@@ -56,7 +88,7 @@ class Clin::Option
|
|
56
88
|
# If @long is false it will return nil
|
57
89
|
# @return [String]
|
58
90
|
def long
|
59
|
-
return nil if @long
|
91
|
+
return nil if @long.eql? false
|
60
92
|
@long ||= default_long
|
61
93
|
end
|
62
94
|
|
@@ -65,31 +97,52 @@ class Clin::Option
|
|
65
97
|
# If @argument is false it will return nil
|
66
98
|
# @return [String]
|
67
99
|
def argument
|
68
|
-
return nil if
|
100
|
+
return nil if flag?
|
69
101
|
@argument ||= default_argument
|
70
102
|
end
|
71
103
|
|
72
104
|
def option_parser_arguments
|
73
|
-
args = [short, long_argument, description]
|
105
|
+
args = [short, long_argument, @type, description]
|
74
106
|
args.compact
|
75
107
|
end
|
76
108
|
|
109
|
+
# Function called by the OptionParser when the option is used
|
110
|
+
# If no block is given this is called otherwise it call the block
|
77
111
|
def on(value, out)
|
78
112
|
out[@name] = value
|
79
113
|
end
|
80
114
|
|
115
|
+
# If the option is a flag option.
|
116
|
+
# i.e Doesn't accept argument.
|
117
|
+
def flag?
|
118
|
+
@argument.eql? false
|
119
|
+
end
|
120
|
+
|
121
|
+
# Init the output Hash with the default values. Must be called before parsing.
|
122
|
+
# @param out [Hash]
|
123
|
+
def load_default(out)
|
124
|
+
return if @default.nil?
|
125
|
+
begin
|
126
|
+
out[@name] = @default.clone
|
127
|
+
rescue
|
128
|
+
out[@name] = @default
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
81
132
|
def ==(other)
|
82
133
|
return false unless other.is_a? Clin::Option
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
@block == other.block
|
134
|
+
to_a == other.to_a
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return array of the attributes
|
138
|
+
def to_a
|
139
|
+
[@name, @description, @type, short, long, argument, @optional_argument, @default, @block]
|
90
140
|
end
|
91
141
|
|
92
|
-
|
142
|
+
# Get the long argument syntax.
|
143
|
+
# ```
|
144
|
+
# :require => '--require REQUIRE'
|
145
|
+
# ```
|
93
146
|
def long_argument
|
94
147
|
return nil unless long
|
95
148
|
out = long
|
@@ -100,3 +153,4 @@ class Clin::Option
|
|
100
153
|
out
|
101
154
|
end
|
102
155
|
end
|
156
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/option'
|
3
|
+
|
4
|
+
class Clin::OptionList < Clin::Option
|
5
|
+
|
6
|
+
# @see Clin::Option#initialize
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
if flag?
|
10
|
+
self.default = 0
|
11
|
+
else
|
12
|
+
self.default = []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def on(value, out)
|
17
|
+
if flag?
|
18
|
+
out[@name] ||= 0
|
19
|
+
out[@name] += 1
|
20
|
+
else
|
21
|
+
out[@name] ||= []
|
22
|
+
out[@name] << value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/clin/version.rb
CHANGED
data/lib/clin.rb
CHANGED
@@ -5,25 +5,28 @@ require 'clin/version'
|
|
5
5
|
|
6
6
|
# Clin Global module. All classes and clin modules should be inside this module
|
7
7
|
module Clin
|
8
|
-
|
9
|
-
'
|
10
|
-
|
8
|
+
class << self
|
9
|
+
# Set the global exe name. `Clin.exe_name = 'git'`
|
10
|
+
attr_writer :exe_name
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@exe_name ||= Clin.default_exe_name
|
16
|
-
end
|
12
|
+
def default_exe_name
|
13
|
+
'command'
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
# Global exe_name
|
17
|
+
# If this is not override it will be 'command'
|
18
|
+
def exe_name
|
19
|
+
@exe_name ||= Clin.default_exe_name
|
20
|
+
end
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
require 'clin/command'
|
25
|
+
require 'clin/command_parser'
|
25
26
|
require 'clin/command_options_mixin'
|
26
27
|
require 'clin/general_option'
|
27
28
|
require 'clin/command_dispatcher'
|
28
29
|
require 'clin/common/help_options'
|
29
30
|
require 'clin/errors'
|
31
|
+
require 'clin/option'
|
32
|
+
require 'clin/option_list'
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Clin::CommandParser do
|
4
|
+
describe '#parse_options' do
|
5
|
+
before :all do
|
6
|
+
@command = Class.new(Clin::Command)
|
7
|
+
@command.add_option Clin::Option.new(:name, 'Set name')
|
8
|
+
@command.add_option Clin::Option.new(:verbose, 'Set verbose', argument: false)
|
9
|
+
@command.add_option Clin::Option.new(:echo, 'Set name', argument_optional: true)
|
10
|
+
end
|
11
|
+
|
12
|
+
subject { Clin::CommandParser.new(@command, []) }
|
13
|
+
|
14
|
+
it 'raise argument when option value is missing' do
|
15
|
+
expect { subject.parse_options(%w(--name)) }.to raise_error(OptionParser::MissingArgument)
|
16
|
+
end
|
17
|
+
it 'raise error when unknown option' do
|
18
|
+
expect { subject.parse_options(%w(--other)) }.to raise_error(Clin::OptionError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it { expect(subject.parse_options(%w(--name MyName))).to eq(name: 'MyName') }
|
22
|
+
it { expect(subject.parse_options(%w(--name=MyName))).to eq(name: 'MyName') }
|
23
|
+
it { expect(subject.parse_options(%w(-nMyName))).to eq(name: 'MyName') }
|
24
|
+
|
25
|
+
|
26
|
+
it { expect(subject.parse_options(%w(-v))).to eq(verbose: true) }
|
27
|
+
|
28
|
+
it { expect(subject.parse_options(%w(--echo))).to eq(echo: nil) }
|
29
|
+
it { expect(subject.parse_options(%w(-e EchoThis))).to eq(echo: 'EchoThis') }
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
describe '#parse_arguments' do
|
34
|
+
before :all do
|
35
|
+
@command = Class.new(Clin::Command)
|
36
|
+
@command.arguments(%w(fix <var> [opt]))
|
37
|
+
end
|
38
|
+
|
39
|
+
subject { Clin::CommandParser.new(@command, []) }
|
40
|
+
|
41
|
+
it 'raise argument when fixed in different' do
|
42
|
+
expect { subject.parse_arguments(%w(other val opt)) }.to raise_error(Clin::CommandLineError)
|
43
|
+
end
|
44
|
+
it 'raise error when too few arguments' do
|
45
|
+
expect { subject.parse_arguments(['fix']) }.to raise_error(Clin::CommandLineError)
|
46
|
+
end
|
47
|
+
it 'raise error when too much argument' do
|
48
|
+
expect { subject.parse_arguments(%w(other val opt more)) }
|
49
|
+
.to raise_error(Clin::CommandLineError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'map arguments' do
|
53
|
+
expect(subject.parse_arguments(%w(fix val opt))).to eq(fix: 'fix', var: 'val', opt: 'opt')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'opt argument is nil when not provided' do
|
57
|
+
expect(subject.parse_arguments(%w(fix val))).to eq(fix: 'fix', var: 'val')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#skipped_options' do
|
62
|
+
def skipped_options(argv)
|
63
|
+
Clin::CommandParser.new(@command, argv).skipped_options
|
64
|
+
end
|
65
|
+
|
66
|
+
before :all do
|
67
|
+
@command = Class.new(Clin::Command)
|
68
|
+
@command.skip_options true
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when all options should be skipped' do
|
72
|
+
it { expect(skipped_options(%w(pos arg))).to eq([]) }
|
73
|
+
|
74
|
+
it { expect(skipped_options(%w(pos arg --ignore -t))).to eq(%w(--ignore -t)) }
|
75
|
+
|
76
|
+
it { expect(skipped_options(%w(pos arg --ignore value -t))).to eq(%w(--ignore value -t)) }
|
77
|
+
|
78
|
+
end
|
79
|
+
context 'when option are define they should not be skipped' do
|
80
|
+
before :all do
|
81
|
+
@command.flag_option :verbose, 'Verbose'
|
82
|
+
end
|
83
|
+
|
84
|
+
it { expect(skipped_options(%w(pos arg --ignore value -t -v))).to eq(%w(--ignore value -t)) }
|
85
|
+
|
86
|
+
it do
|
87
|
+
expect(skipped_options(%w(pos arg --verbose --ignore value -t)))
|
88
|
+
.to eq(%w(--ignore value -t))
|
89
|
+
end
|
90
|
+
|
91
|
+
it do
|
92
|
+
expect(skipped_options(%w(pos arg --ignore value --verbose -t)))
|
93
|
+
.to eq(%w(--ignore value -t))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '.handle_dispatch' do
|
99
|
+
let(:args) { [Faker::Lorem.word, Faker::Lorem.word] }
|
100
|
+
before :all do
|
101
|
+
@command = Class.new(Clin::Command)
|
102
|
+
@command.arguments(%w(remote <args>...))
|
103
|
+
end
|
104
|
+
|
105
|
+
before do
|
106
|
+
allow_any_instance_of(Clin::CommandDispatcher).to receive(:parse)
|
107
|
+
end
|
108
|
+
|
109
|
+
subject { Clin::CommandParser.new(@command, []) }
|
110
|
+
|
111
|
+
context 'when only dispatching arguments' do
|
112
|
+
before do
|
113
|
+
@command.dispatch :args
|
114
|
+
end
|
115
|
+
it 'call the command dispatcher with the right arguments' do
|
116
|
+
expect_any_instance_of(Clin::CommandDispatcher).to receive(:parse).once.with(args)
|
117
|
+
subject.redispatch(remote: 'remote', args: args)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when using prefix' do
|
122
|
+
let(:prefix) { 'remote' }
|
123
|
+
before do
|
124
|
+
@command.dispatch :args, prefix: prefix
|
125
|
+
end
|
126
|
+
it 'call the command dispatcher with the right arguments' do
|
127
|
+
expect_any_instance_of(Clin::CommandDispatcher).to receive(:parse).once.with([prefix] + args)
|
128
|
+
subject.redispatch(remote: 'remote', args: args)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'when using commands' do
|
133
|
+
let(:cmd1) { double(:command) }
|
134
|
+
let(:cmd2) { double(:command) }
|
135
|
+
before do
|
136
|
+
@command.dispatch :args, commands: [cmd1, cmd2]
|
137
|
+
allow_any_instance_of(Clin::CommandDispatcher).to receive(:initialize)
|
138
|
+
end
|
139
|
+
it 'call the command dispatcher with the right arguments' do
|
140
|
+
expect_any_instance_of(Clin::CommandDispatcher).to receive(:initialize).once.with([cmd1, cmd2])
|
141
|
+
subject.redispatch(remote: 'remote', args: args)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'when dispatcher raise HelpError' do
|
146
|
+
let(:new_message) { Faker::Lorem.sentence }
|
147
|
+
before do
|
148
|
+
@command.dispatch :args
|
149
|
+
allow_any_instance_of(Clin::CommandDispatcher).to receive(:initialize)
|
150
|
+
allow_any_instance_of(Clin::CommandDispatcher).to receive(:parse) do
|
151
|
+
fail Clin::HelpError, 'Dispatcher error'
|
152
|
+
end
|
153
|
+
allow(@command).to receive(:option_parser).and_return(new_message)
|
154
|
+
end
|
155
|
+
it do
|
156
|
+
expect { subject.redispatch(remote: 'remote', args: args) }
|
157
|
+
.to raise_error(Clin::HelpError)
|
158
|
+
end
|
159
|
+
it do
|
160
|
+
expect { subject.redispatch(remote: 'remote', args: args) }
|
161
|
+
.to raise_error(new_message)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'clin/command'
|
3
|
+
|
4
|
+
RSpec.describe Clin::Command do
|
5
|
+
describe '#arguments=' do
|
6
|
+
subject { Class.new(Clin::Command) }
|
7
|
+
let(:args) { %w(fix <var> [opt]) }
|
8
|
+
before do
|
9
|
+
allow(Clin::Argument).to receive(:new)
|
10
|
+
end
|
11
|
+
context 'when using string to set arguments' do
|
12
|
+
before do
|
13
|
+
subject.arguments (args.join(' '))
|
14
|
+
end
|
15
|
+
it { expect(subject.args.size).to eq(args.size) }
|
16
|
+
it { expect(Clin::Argument).to have_received(:new).exactly(args.size).times }
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when using array to set arguments' do
|
20
|
+
before do
|
21
|
+
subject.arguments (args)
|
22
|
+
end
|
23
|
+
it { expect(subject.args.size).to eq(args.size) }
|
24
|
+
it { expect(Clin::Argument).to have_received(:new).exactly(args.size).times }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when using array that contains multiple arguments to set arguments' do
|
28
|
+
before do
|
29
|
+
subject.arguments ([args[0], args[1..-1]])
|
30
|
+
end
|
31
|
+
it { expect(subject.args.size).to eq(args.size) }
|
32
|
+
it { expect(Clin::Argument).to have_received(:new).exactly(args.size).times }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#banner' do
|
38
|
+
subject { Class.new(Clin::Command) }
|
39
|
+
context 'when exe is defined' do
|
40
|
+
let(:exe) { Faker::Lorem.word }
|
41
|
+
before do
|
42
|
+
subject.exe_name(exe)
|
43
|
+
end
|
44
|
+
|
45
|
+
it { expect(subject.banner).to eq("Usage: #{exe} [Options]") }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when exe is not defined' do
|
49
|
+
it { expect(subject.banner).to eq('Usage: command [Options]') }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when arguments are defined' do
|
53
|
+
let(:arguments) { '<some> [Value]' }
|
54
|
+
before do
|
55
|
+
subject.arguments(arguments)
|
56
|
+
end
|
57
|
+
|
58
|
+
it { expect(subject.banner).to eq("Usage: #{Clin.default_exe_name} #{arguments} [Options]") }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
describe '.dispatch_doc' do
|
65
|
+
subject { Class.new(Clin::Command) }
|
66
|
+
before do
|
67
|
+
subject.arguments(%w(remote <args>...))
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:cmd1) { double(:command, usage: 'cmd1') }
|
71
|
+
let(:cmd2) { double(:command, usage: 'cmd2') }
|
72
|
+
let(:cmd3) { double(:command, usage: 'cmd3') }
|
73
|
+
let(:cmds) { [cmd1, cmd2, cmd3] }
|
74
|
+
let(:opts) { double(:option_parser, separator: true) }
|
75
|
+
before do
|
76
|
+
subject.dispatch :args, commands: cmds
|
77
|
+
allow_any_instance_of(Clin::CommandDispatcher).to receive(:initialize)
|
78
|
+
subject.dispatch_doc(opts)
|
79
|
+
end
|
80
|
+
it { expect(opts).to have_received(:separator).at_least(cmds.size).times }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.subcommands' do
|
84
|
+
before do
|
85
|
+
@cmd1 = Class.new(Clin::Command)
|
86
|
+
@cmd2 = Class.new(Clin::Command)
|
87
|
+
@abstract_cmd = Class.new(Clin::Command) { abstract true }
|
88
|
+
end
|
89
|
+
|
90
|
+
it { expect(Clin::Command.subcommands).to include(@cmd1) }
|
91
|
+
it { expect(Clin::Command.subcommands).to include(@cmd2) }
|
92
|
+
it { expect(Clin::Command.subcommands).not_to include(@abstract_cmd) }
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '.exe_name' do
|
96
|
+
context 'when not setting the exe_name' do
|
97
|
+
subject { Class.new(Clin::Command) }
|
98
|
+
|
99
|
+
it { expect(subject.exe_name).to eq(Clin.exe_name) }
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when setting the exe_name' do
|
103
|
+
let(:name) { Faker::Lorem.word }
|
104
|
+
subject { Class.new(Clin::Command) }
|
105
|
+
before do
|
106
|
+
subject.exe_name(name)
|
107
|
+
end
|
108
|
+
it { expect(subject.exe_name).to eq(name) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|