clin 0.1.0 → 0.2.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 +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
|