clin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +5 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +14 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +10 -0
- data/clin.gemspec +27 -0
- data/examples/dispatcher.rb +76 -0
- data/examples/nested_dispatcher.rb +91 -0
- data/examples/optional_argument.rb +24 -0
- data/examples/simple.rb +39 -0
- data/examples/test.rb +2 -0
- data/lib/clin/argument.rb +90 -0
- data/lib/clin/command.rb +195 -0
- data/lib/clin/command_dispatcher.rb +48 -0
- data/lib/clin/command_options_mixin.rb +88 -0
- data/lib/clin/common/help_options.rb +25 -0
- data/lib/clin/errors.rb +31 -0
- data/lib/clin/general_option.rb +15 -0
- data/lib/clin/option.rb +102 -0
- data/lib/clin/version.rb +4 -0
- data/lib/clin.rb +29 -0
- data/spec/cli/argument_spec.rb +117 -0
- data/spec/cli/command_dispacher_spec.rb +84 -0
- data/spec/cli/command_options_mixin_spec.rb +64 -0
- data/spec/cli/command_spec.rb +225 -0
- data/spec/cli/common/help_options_spec.rb +29 -0
- data/spec/cli/option_spec.rb +132 -0
- data/spec/errors_spec.rb +8 -0
- data/spec/examples/simple_spec.rb +31 -0
- data/spec/spec_helper.rb +28 -0
- metadata +171 -0
data/lib/clin/command.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/command_options_mixin'
|
3
|
+
require 'clin/argument'
|
4
|
+
require 'shellwords'
|
5
|
+
require 'clin/common/help_options'
|
6
|
+
|
7
|
+
# Clin Command
|
8
|
+
class Clin::Command < Clin::CommandOptionsMixin
|
9
|
+
|
10
|
+
class_attribute :args
|
11
|
+
class_attribute :description
|
12
|
+
|
13
|
+
|
14
|
+
# Redispatch will be reset to nil when inheriting a dispatcher command
|
15
|
+
class_attribute :_redispatch_args
|
16
|
+
class_attribute :_abstract
|
17
|
+
class_attribute :_exe_name
|
18
|
+
|
19
|
+
self.args = []
|
20
|
+
self.description = ''
|
21
|
+
self._abstract = false
|
22
|
+
|
23
|
+
|
24
|
+
# Trigger when a class inherit this class
|
25
|
+
# Rest class_attributes that should not be shared with subclass
|
26
|
+
# @param subclass [Clin::Command]
|
27
|
+
def self.inherited(subclass)
|
28
|
+
subclass._redispatch_args = nil
|
29
|
+
subclass._abstract = false
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mark the class as abstract
|
34
|
+
def self.abstract(value)
|
35
|
+
self._abstract = value
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set or get the exe name.
|
39
|
+
# Executable name that will be display in the usage.
|
40
|
+
# If exe_name is not set in a class or it's parent it will use the global setting Clin.exe_name
|
41
|
+
# @param value [String] name of the exe.
|
42
|
+
# ```
|
43
|
+
# class Git < Clin::Command
|
44
|
+
# exe_name 'git'
|
45
|
+
# arguments '<command> <args>...'
|
46
|
+
# end
|
47
|
+
# Git.usage # => git <command> <args>...
|
48
|
+
# ```
|
49
|
+
def self.exe_name(value=nil)
|
50
|
+
self._exe_name = value unless value.nil?
|
51
|
+
self._exe_name ||= Clin.exe_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.arguments(args)
|
55
|
+
self.args = []
|
56
|
+
[*args].map(&:split).flatten.each do |arg|
|
57
|
+
self.args += [Clin::Argument.new(arg)]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.usage
|
62
|
+
a = [exe_name, args.map(&:original).join(' '), '[Options]']
|
63
|
+
a.reject(&:blank?).join(' ')
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.banner
|
67
|
+
"Usage: #{usage}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Parse the command and initialize the command object with the parsed options
|
71
|
+
# @param argv [Array|String] command line to parse.
|
72
|
+
def self.parse(argv = ARGV, fallback_help: true)
|
73
|
+
argv = Shellwords.split(argv) if argv.is_a? String
|
74
|
+
argv = argv.clone
|
75
|
+
options_map = parse_options(argv)
|
76
|
+
error = nil
|
77
|
+
begin
|
78
|
+
args_map = parse_arguments(argv)
|
79
|
+
rescue Clin::MissingArgumentError => e
|
80
|
+
error = e
|
81
|
+
rescue Clin::FixedArgumentError => e
|
82
|
+
raise e unless fallback_help
|
83
|
+
error = e
|
84
|
+
end
|
85
|
+
args_map ||= {}
|
86
|
+
|
87
|
+
options = options_map.merge(args_map)
|
88
|
+
return handle_dispatch(options) unless self._redispatch_args.nil?
|
89
|
+
obj = new(options)
|
90
|
+
if error
|
91
|
+
fail Clin::HelpError, option_parser if fallback_help
|
92
|
+
fail error
|
93
|
+
end
|
94
|
+
obj
|
95
|
+
end
|
96
|
+
|
97
|
+
# Parse the options in the argv.
|
98
|
+
# @return [Array] the list of argv that are not options(positional arguments)
|
99
|
+
def self.parse_options(argv)
|
100
|
+
out = {}
|
101
|
+
parser = option_parser(out)
|
102
|
+
parser.parse!(argv)
|
103
|
+
out
|
104
|
+
end
|
105
|
+
|
106
|
+
# Build the Option Parser object
|
107
|
+
# Used to parse the option
|
108
|
+
# Useful for regenerating the help as well.
|
109
|
+
def self.option_parser(out = {})
|
110
|
+
OptionParser.new do |opts|
|
111
|
+
opts.banner = banner
|
112
|
+
opts.separator ''
|
113
|
+
opts.separator 'Options:'
|
114
|
+
register_options(opts, out)
|
115
|
+
dispatch_doc(opts)
|
116
|
+
unless description.blank?
|
117
|
+
opts.separator "\nDescription:"
|
118
|
+
opts.separator description
|
119
|
+
end
|
120
|
+
opts.separator ''
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.execute_general_options(options)
|
125
|
+
general_options.each do |_cls, gopts|
|
126
|
+
gopts.execute(options)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Parse the argument. The options must have been strip out first.
|
131
|
+
def self.parse_arguments(argv)
|
132
|
+
out = {}
|
133
|
+
self.args.each do |arg|
|
134
|
+
value, argv = arg.parse(argv)
|
135
|
+
out[arg.name.to_sym] = value
|
136
|
+
end
|
137
|
+
out.delete_if { |_, v| v.nil? }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Redispatch the command to a sub command with the given arguments
|
141
|
+
# @param args [Array<String>|String] New argument to parse
|
142
|
+
# @param prefix [String] Prefix to add to the beginning of the command
|
143
|
+
# @param commands [Array<Clin::Command.class>] Commands that will be tried against
|
144
|
+
# If no commands are given it will look for Clin::Command in the class namespace
|
145
|
+
# e.g. If those 2 classes are defined.
|
146
|
+
# `MyDispatcher < Clin::Command` and `MyDispatcher::ChildCommand < Clin::Command`
|
147
|
+
# Will test against ChildCommand
|
148
|
+
def self.dispatch(args, prefix: nil, commands: nil)
|
149
|
+
self._redispatch_args = [[*args], prefix, commands]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Method called after the argument have been parsed and before creating the command
|
153
|
+
# @param params [List<String>] Parsed params from the command line.
|
154
|
+
def self.handle_dispatch(params)
|
155
|
+
args, prefix, commands = self._redispatch_args
|
156
|
+
commands ||= default_commands
|
157
|
+
dispatcher = Clin::CommandDispatcher.new(commands)
|
158
|
+
args = args.map { |x| params[x] }.flatten
|
159
|
+
args = prefix.split + args unless prefix.nil?
|
160
|
+
begin
|
161
|
+
dispatcher.parse(args)
|
162
|
+
rescue Clin::HelpError
|
163
|
+
raise Clin::HelpError, option_parser
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.dispatch_doc(opts)
|
168
|
+
return if self._redispatch_args.nil?
|
169
|
+
opts.separator 'Examples: '
|
170
|
+
commands = (self._redispatch_args[2] || default_commands)
|
171
|
+
commands.each do |cmd_cls|
|
172
|
+
opts.separator "\t#{cmd_cls.usage}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.default_commands
|
177
|
+
# self.constants.map { |c| self.const_get(c) }.select { |c| c.is_a?(Class) && (c < Clin::Command) }
|
178
|
+
self.subcommands
|
179
|
+
end
|
180
|
+
|
181
|
+
# List the subcommands
|
182
|
+
# The subcommands are all the Classes inheriting this one that are not set to abstract
|
183
|
+
def self.subcommands
|
184
|
+
self.subclasses.reject(&:_abstract)
|
185
|
+
end
|
186
|
+
|
187
|
+
general_option 'Clin::HelpOptions'
|
188
|
+
|
189
|
+
attr_accessor :params
|
190
|
+
|
191
|
+
def initialize(params)
|
192
|
+
@params = params
|
193
|
+
self.class.execute_general_options(params)
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/command'
|
3
|
+
|
4
|
+
# Class charge dispatching the CL to the right command
|
5
|
+
class Clin::CommandDispatcher
|
6
|
+
attr_accessor :commands
|
7
|
+
|
8
|
+
# Create a new command dispatcher.
|
9
|
+
# @param commands [Array<Clin::Command.class>] List of commands that can be dispatched.
|
10
|
+
# If commands is nil it will get all the subclass of Clin::Command loaded.
|
11
|
+
def initialize(*commands)
|
12
|
+
@commands = commands.empty? ? Clin::Command.subcommands : commands.flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parse the command line using the given arguments
|
16
|
+
# It will return the newly initialized command with the arguments if there is a match
|
17
|
+
# Otherwise will fail and display the help message
|
18
|
+
# @param argv [Array<String>] Arguments
|
19
|
+
# @return [Clin::Command]
|
20
|
+
def parse(argv = ARGV)
|
21
|
+
errors = 0
|
22
|
+
argv = Shellwords.split(argv) if argv.is_a? String
|
23
|
+
@commands.each do |cmd|
|
24
|
+
begin
|
25
|
+
return cmd.parse(argv, fallback_help: false)
|
26
|
+
rescue Clin::ArgumentError
|
27
|
+
errors += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
fail Clin::HelpError, help_message
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper method to parse against all the commands
|
34
|
+
# @see #parse
|
35
|
+
def self.parse(argv=ARGV)
|
36
|
+
Clin::CommandDispatcher.new.parse(argv)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Generate the help message for this dispatcher
|
40
|
+
# @return [String]
|
41
|
+
def help_message
|
42
|
+
message = "Usage:\n"
|
43
|
+
commands.each do |command|
|
44
|
+
message << "\t#{command.usage}\n"
|
45
|
+
end
|
46
|
+
message
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/option'
|
3
|
+
|
4
|
+
# Template class for reusable options and commands
|
5
|
+
# It provide the method to add options to a command
|
6
|
+
class Clin::CommandOptionsMixin
|
7
|
+
class_attribute :options
|
8
|
+
class_attribute :general_options
|
9
|
+
self.options = []
|
10
|
+
self.general_options = {}
|
11
|
+
|
12
|
+
|
13
|
+
# Add an option
|
14
|
+
# @param args list of arguments.
|
15
|
+
# * First argument must be the name if no block is given.
|
16
|
+
# It will set automatically read the value into the hash with +name+ as key
|
17
|
+
# * The remaining arguments are OptionsParser#on arguments
|
18
|
+
# ```
|
19
|
+
# option :require, '-r', '--require [LIBRARY]', 'Require the library'
|
20
|
+
# option '-h', '--helper', 'Show the help' do
|
21
|
+
# puts opts
|
22
|
+
# exit
|
23
|
+
# end
|
24
|
+
# ```
|
25
|
+
def self.opt_option(*args, &block)
|
26
|
+
add_option Clin::Option.new(*args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add an option.
|
30
|
+
# Helper method that just create a new Clin::Option with the argument then call add_option
|
31
|
+
# ```
|
32
|
+
# option :show, 'Show some message'
|
33
|
+
# # => -s --show SHOW Show some message
|
34
|
+
# option :require, 'Require a library', short: false, optional: true, argument: 'LIBRARY'
|
35
|
+
# # => --require [LIBRARY] Require a library
|
36
|
+
# option :help, 'Show the help', argument: false do
|
37
|
+
# puts opts
|
38
|
+
# exit
|
39
|
+
# end
|
40
|
+
# # => -h --help Show the help
|
41
|
+
# ```
|
42
|
+
def self.option(name, description, **config, &block)
|
43
|
+
add_option Clin::Option.new(name, description, **config, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# For an option that does not have an argument
|
47
|
+
# Same as .option except it will default argument to false
|
48
|
+
# ```
|
49
|
+
# option :verbose, 'Use verbose' #=> -v --verbose will be added to the option of this command
|
50
|
+
# ```
|
51
|
+
def self.flag_option(name, description, **config, &block)
|
52
|
+
add_option Clin::Option.new(name, description, **config.merge(argument: false), &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.add_option(option)
|
56
|
+
# Need to use += instead of << otherwise the parent class will also be changed
|
57
|
+
self.options += [option]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add a general option
|
61
|
+
# @param option_cls [Class<GeneralOption>] Class inherited from GeneralOption
|
62
|
+
# @param config [Hash] General option config. Check the general option config.
|
63
|
+
def self.general_option(option_cls, config = {})
|
64
|
+
option_cls = option_cls.constantize if option_cls.is_a? String
|
65
|
+
self.general_options = self.general_options.merge(option_cls => option_cls.new(config))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Remove a general option
|
69
|
+
# Might be useful if a parent added the option but is not needed in this child.
|
70
|
+
def self.remove_general_option(option_cls)
|
71
|
+
option_cls = option_cls.constantize if option_cls.is_a? String
|
72
|
+
self.general_options = self.general_options.except(option_cls)
|
73
|
+
end
|
74
|
+
|
75
|
+
# To be called inside OptionParser block
|
76
|
+
# Extract the option in the command line using the OptionParser and map it to the out map.
|
77
|
+
# @param opts [OptionParser]
|
78
|
+
# @param out [Hash] Where the options shall be extracted
|
79
|
+
def self.register_options(opts, out)
|
80
|
+
options.each do |option|
|
81
|
+
option.register(opts, out)
|
82
|
+
end
|
83
|
+
|
84
|
+
general_options.each do |_cls, option|
|
85
|
+
option.class.register_options(opts, out)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/general_option'
|
3
|
+
# Help option class
|
4
|
+
# Add the help option to you command
|
5
|
+
# ```
|
6
|
+
# class MyCommand < Clin::Command
|
7
|
+
# general_option Clin::HelpOptions
|
8
|
+
# end
|
9
|
+
# ```
|
10
|
+
# Then running you command with -h or --help will show the help menu
|
11
|
+
class Clin::HelpOptions < Clin::GeneralOption
|
12
|
+
flag_option :help, 'Show the help.' do |opts, out, _|
|
13
|
+
out[:help] = opts
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(raise: true)
|
17
|
+
@raise = raise
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def execute(options)
|
22
|
+
return unless @raise
|
23
|
+
fail Clin::HelpError, options[:help] if options[:help]
|
24
|
+
end
|
25
|
+
end
|
data/lib/clin/errors.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Contains the errors for Clin
|
2
|
+
module Clin
|
3
|
+
# Parent error class for all Clin errors
|
4
|
+
Error = Class.new(RuntimeError)
|
5
|
+
|
6
|
+
# Error cause by the user input(when parsing command)
|
7
|
+
CommandLineError = Class.new(Error)
|
8
|
+
|
9
|
+
# Error when the help needs to be shown
|
10
|
+
HelpError = Class.new(CommandLineError)
|
11
|
+
|
12
|
+
# Error when an positional argument is wrong
|
13
|
+
ArgumentError = Class.new(CommandLineError)
|
14
|
+
|
15
|
+
# Error when a fixed argument is not matched
|
16
|
+
class FixedArgumentError < ArgumentError
|
17
|
+
def initialize(argument = '', got = '')
|
18
|
+
super("Expecting '#{argument}' but got '#{got}'")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Error when a command is missing an argument
|
23
|
+
class MissingArgumentError < ArgumentError
|
24
|
+
def initialize(message = '')
|
25
|
+
super("Missing argument #{message}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Error when a option is wrong
|
30
|
+
OptionError = Class.new(CommandLineError)
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
class Clin::GeneralOption < Clin::CommandOptionsMixin
|
4
|
+
|
5
|
+
def initialize(config = {})
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
# 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. This allow general options to be extracted when parsing a command line
|
11
|
+
# as well as calling the command directly in the code
|
12
|
+
# @param _params [Hash] Params got in the command
|
13
|
+
def execute(_params)
|
14
|
+
end
|
15
|
+
end
|
data/lib/clin/option.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Option container.
|
4
|
+
class Clin::Option
|
5
|
+
attr_accessor :name, :description, :optional_argument, :block
|
6
|
+
attr_reader :short, :long, :argument
|
7
|
+
|
8
|
+
def initialize(name, description, short: nil, long: nil, argument: nil, optional_argument: false, &block)
|
9
|
+
@name = name
|
10
|
+
@description = description
|
11
|
+
@short = short
|
12
|
+
@long = long
|
13
|
+
@optional_argument = optional_argument
|
14
|
+
@argument = argument
|
15
|
+
@block = block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Register the option to the Option Parser
|
19
|
+
# @param opts [OptionParser]
|
20
|
+
# @param out [Hash] Out options mapping
|
21
|
+
def register(opts, out)
|
22
|
+
if @block.nil?
|
23
|
+
opts.on(*option_parser_arguments) do |value|
|
24
|
+
on(value, out)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
opts.on(*option_parser_arguments) do |value|
|
28
|
+
block.call(opts, out, value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_short
|
34
|
+
"-#{name[0].downcase}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_long
|
38
|
+
"--#{name.downcase}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_argument
|
42
|
+
name.to_s.upcase
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the short option
|
46
|
+
# If @short is nil it will use #default_short
|
47
|
+
# If @short is false it will return nil
|
48
|
+
# @return [String]
|
49
|
+
def short
|
50
|
+
return nil if @short === false
|
51
|
+
@short ||= default_short
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the long option
|
55
|
+
# If @long is nil it will use #default_long
|
56
|
+
# If @long is false it will return nil
|
57
|
+
# @return [String]
|
58
|
+
def long
|
59
|
+
return nil if @long === false
|
60
|
+
@long ||= default_long
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the argument option
|
64
|
+
# If @argument is nil it will use #default_argument
|
65
|
+
# If @argument is false it will return nil
|
66
|
+
# @return [String]
|
67
|
+
def argument
|
68
|
+
return nil if @argument === false
|
69
|
+
@argument ||= default_argument
|
70
|
+
end
|
71
|
+
|
72
|
+
def option_parser_arguments
|
73
|
+
args = [short, long_argument, description]
|
74
|
+
args.compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def on(value, out)
|
78
|
+
out[@name] = value
|
79
|
+
end
|
80
|
+
|
81
|
+
def ==(other)
|
82
|
+
return false unless other.is_a? Clin::Option
|
83
|
+
@name == other.name &&
|
84
|
+
@description == other.description &&
|
85
|
+
short == other.short &&
|
86
|
+
long == other.long &&
|
87
|
+
argument == other.argument &&
|
88
|
+
@optional_argument == other.optional_argument &&
|
89
|
+
@block == other.block
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def long_argument
|
94
|
+
return nil unless long
|
95
|
+
out = long
|
96
|
+
if argument
|
97
|
+
arg = @optional_argument ? "[#{argument}]" : argument
|
98
|
+
out += " #{arg}"
|
99
|
+
end
|
100
|
+
out
|
101
|
+
end
|
102
|
+
end
|
data/lib/clin/version.rb
ADDED
data/lib/clin.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'optparse'
|
4
|
+
require 'clin/version'
|
5
|
+
|
6
|
+
# Clin Global module. All classes and clin modules should be inside this module
|
7
|
+
module Clin
|
8
|
+
def self.default_exe_name
|
9
|
+
'command'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Global exe_name
|
13
|
+
# If this is not override it will be 'command'
|
14
|
+
def self.exe_name
|
15
|
+
@exe_name ||= Clin.default_exe_name
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set the global exe name
|
19
|
+
def self.exe_name=(value)
|
20
|
+
@exe_name=value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'clin/command'
|
25
|
+
require 'clin/command_options_mixin'
|
26
|
+
require 'clin/general_option'
|
27
|
+
require 'clin/command_dispatcher'
|
28
|
+
require 'clin/common/help_options'
|
29
|
+
require 'clin/errors'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'clin/argument'
|
3
|
+
|
4
|
+
RSpec.describe Clin::Argument do
|
5
|
+
|
6
|
+
describe '#check_optional' do
|
7
|
+
subject { Clin::Argument.new('') }
|
8
|
+
it { expect(subject.check_optional('not_optional')).to eq('not_optional') }
|
9
|
+
it { expect(subject.check_optional('<not_optional>')).to eq('<not_optional>') }
|
10
|
+
it { expect(subject.check_optional('[optional]')).to eq('optional') }
|
11
|
+
it { expect(subject.check_optional('[<optional>]')).to eq('<optional>') }
|
12
|
+
it { expect(subject.check_optional('[<optional>...]')).to eq('<optional>...') }
|
13
|
+
it { expect(subject.check_optional('[optional...]')).to eq('optional...') }
|
14
|
+
it { expect { subject.check_optional('[optional') }.to raise_error(Clin::Error) }
|
15
|
+
it { expect { subject.check_optional('[optional]...') }.to raise_error(Clin::Error) }
|
16
|
+
|
17
|
+
it 'expect optional to set instance variable' do
|
18
|
+
subject.check_optional('[optional]')
|
19
|
+
expect(subject.optional).to be true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'expect not optional to keep instance variable' do
|
23
|
+
subject.check_optional('not_optional')
|
24
|
+
expect(subject.optional).to be false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#check_multiple' do
|
29
|
+
subject { Clin::Argument.new('') }
|
30
|
+
it { expect(subject.check_multiple('multiple...')).to eq('multiple') }
|
31
|
+
it { expect(subject.check_multiple('<multiple>...')).to eq('<multiple>') }
|
32
|
+
it { expect(subject.check_multiple('not_multiple')).to eq('not_multiple') }
|
33
|
+
it { expect(subject.check_multiple('<not_multiple>')).to eq('<not_multiple>') }
|
34
|
+
|
35
|
+
it 'expect to set instance variable' do
|
36
|
+
subject.check_multiple('multiple...')
|
37
|
+
expect(subject.multiple).to be true
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'expect not to set instance variable' do
|
41
|
+
subject.check_multiple('not_multiple')
|
42
|
+
expect(subject.multiple).to be false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#check_variable' do
|
47
|
+
subject { Clin::Argument.new('') }
|
48
|
+
it { expect(subject.check_variable('not_variable')).to eq('not_variable') }
|
49
|
+
it { expect(subject.check_variable('<variable>')).to eq('variable') }
|
50
|
+
|
51
|
+
it { expect { subject.check_variable('<variable') }.to raise_error(Clin::Error) }
|
52
|
+
it { expect { subject.check_variable('<variable...') }.to raise_error(Clin::Error) }
|
53
|
+
it 'expect optional to set instance variable' do
|
54
|
+
subject.check_variable('<variable>')
|
55
|
+
expect(subject.variable).to be true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'expect not optional to keep instance variable' do
|
59
|
+
subject.check_variable('not_variable')
|
60
|
+
expect(subject.variable).to be false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#initialize' do
|
65
|
+
subject { Clin::Argument.new('[<optional_var_mul>...]') }
|
66
|
+
it { expect(subject.name).to eq('optional_var_mul') }
|
67
|
+
it { expect(subject.optional).to be true }
|
68
|
+
it { expect(subject.multiple).to be true }
|
69
|
+
it { expect(subject.variable).to be true }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'parse' do
|
73
|
+
context 'when argument is optional' do
|
74
|
+
subject { Clin::Argument.new('[<optional>]') }
|
75
|
+
|
76
|
+
it 'get the argument' do
|
77
|
+
value, rem = subject.parse(%w(value1 value2))
|
78
|
+
expect(value).to eq('value1')
|
79
|
+
expect(rem).to eq(['value2'])
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'work when there is no argument' do
|
83
|
+
value, rem = subject.parse([])
|
84
|
+
expect(value).to be nil
|
85
|
+
expect(rem).to eq([])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when multiple arguments are allowed' do
|
90
|
+
subject { Clin::Argument.new('<multiple>...') }
|
91
|
+
|
92
|
+
it 'get the argument' do
|
93
|
+
value, rem = subject.parse(%w(value1 value2))
|
94
|
+
expect(value).to eq(%w(value1 value2))
|
95
|
+
expect(rem).to eq([])
|
96
|
+
end
|
97
|
+
|
98
|
+
it { expect { subject.parse([]).to raise_error(Clin::CommandLineError) } }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when argument must match exactly' do
|
102
|
+
subject { Clin::Argument.new('argument') }
|
103
|
+
|
104
|
+
it 'get the argument' do
|
105
|
+
value, rem = subject.parse(['argument'])
|
106
|
+
expect(value).to eq('argument')
|
107
|
+
expect(rem).to eq([])
|
108
|
+
end
|
109
|
+
it { expect { subject.parse(['other_value']).to raise_error(Clin::CommandLineError) } }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when multiple argument must match exactly' do
|
113
|
+
subject { Clin::Argument.new('argument...') }
|
114
|
+
it { expect { subject.parse(%w(argument other_value)).to raise_error(Clin::CommandLineError) } }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|