clin 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 +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
|