clin 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/CHANGELOG.md +7 -0
- data/README.md +1 -0
- data/clin.gemspec +1 -1
- data/examples/auto_option.rb +17 -0
- data/examples/dispatcher.rb +17 -23
- data/examples/list_option.rb +3 -8
- data/examples/nested_dispatcher.rb +8 -53
- data/examples/simple.rb +3 -22
- data/examples/test.rb +9 -9
- data/lib/clin/argument.rb +3 -3
- data/lib/clin/command.rb +11 -126
- data/lib/clin/command_mixin/core.rb +152 -0
- data/lib/clin/command_mixin/dispatcher.rb +34 -0
- data/lib/clin/command_mixin/options.rb +134 -0
- data/lib/clin/command_mixin.rb +9 -0
- data/lib/clin/general_option.rb +3 -1
- data/lib/clin/option.rb +37 -3
- data/lib/clin/option_list.rb +1 -1
- data/lib/clin/shell.rb +111 -0
- data/lib/clin/shell_interaction/choose.rb +59 -0
- data/lib/clin/shell_interaction/file_conflict.rb +53 -0
- data/lib/clin/shell_interaction/yes_or_no.rb +16 -0
- data/lib/clin/shell_interaction.rb +34 -0
- data/lib/clin/version.rb +1 -1
- data/lib/clin.rb +10 -1
- data/spec/clin/command_dispacher_spec.rb +3 -3
- data/spec/clin/command_mixin/core_spec.rb +37 -0
- data/spec/clin/{command_options_mixin_spec.rb → command_mixin/options_spec.rb} +11 -8
- data/spec/clin/command_parser_spec.rb +2 -2
- data/spec/clin/command_spec.rb +3 -3
- data/spec/clin/option_spec.rb +40 -0
- data/spec/clin/shell_interaction/choose_spec.rb +54 -0
- data/spec/clin/shell_spec.rb +147 -0
- data/spec/examples/auto_option_spec.rb +11 -0
- metadata +24 -7
- data/lib/clin/command_options_mixin.rb +0 -118
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'clin'
|
2
|
+
require 'clin/option'
|
3
|
+
|
4
|
+
# Contains class methods to add option to a command or a General option
|
5
|
+
module Clin::CommandMixin::Options
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
self.options = []
|
10
|
+
self.general_options = {}
|
11
|
+
# Trigger when a class inherit this class
|
12
|
+
# It will clone attributes that need inheritance
|
13
|
+
# @param subclass [Clin::Command]
|
14
|
+
def self.inherited(subclass)
|
15
|
+
subclass.options = @options.clone
|
16
|
+
subclass.general_options = @general_options.clone
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods # :nodoc:
|
22
|
+
attr_accessor :options
|
23
|
+
attr_accessor :general_options
|
24
|
+
|
25
|
+
# Add an option
|
26
|
+
# @param args list of arguments.
|
27
|
+
# * First argument must be the name if no block is given.
|
28
|
+
# It will set automatically read the value into the hash with +name+ as key
|
29
|
+
# * The remaining arguments are OptionsParser#on arguments
|
30
|
+
# ```
|
31
|
+
# option :require, '-r', '--require [LIBRARY]', 'Require the library'
|
32
|
+
# option '-h', '--helper', 'Show the help' do
|
33
|
+
# puts opts
|
34
|
+
# exit
|
35
|
+
# end
|
36
|
+
# ```
|
37
|
+
def opt_option(*args, &block)
|
38
|
+
add_option Clin::Option.new(*args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add an option.
|
42
|
+
# Helper method that just create a new Clin::Option with the argument then call add_option
|
43
|
+
# ```
|
44
|
+
# option :show, 'Show some message'
|
45
|
+
# # => -s --show SHOW Show some message
|
46
|
+
# option :require, 'Require a library', short: false, optional: true, argument: 'LIBRARY'
|
47
|
+
# # => --require [LIBRARY] Require a library
|
48
|
+
# option :help, 'Show the help', argument: false do
|
49
|
+
# puts opts
|
50
|
+
# exit
|
51
|
+
# end
|
52
|
+
# # => -h --help Show the help
|
53
|
+
# ```
|
54
|
+
def option(name, description, **config, &block)
|
55
|
+
add_option Clin::Option.new(name, description, **config, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# For an option that does not have an argument
|
59
|
+
# Same as .option except it will default argument to false
|
60
|
+
# ```
|
61
|
+
# option :verbose, 'Use verbose' #=> -v --verbose will be added to the option of this command
|
62
|
+
# ```
|
63
|
+
def flag_option(name, description, **config, &block)
|
64
|
+
add_option Clin::Option.new(name, description, **config.merge(argument: false), &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add a list option.
|
68
|
+
# @see Clin::OptionList#initialize
|
69
|
+
def list_option(name, description, **config)
|
70
|
+
add_option Clin::OptionList.new(name, description, **config)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add a list options that don't take arguments
|
74
|
+
# Same as .list_option but set +argument+ to false
|
75
|
+
# @see Clin::OptionList#initialize
|
76
|
+
def list_flag_option(name, description, **config)
|
77
|
+
add_option Clin::OptionList.new(name, description, **config.merge(argument: false))
|
78
|
+
end
|
79
|
+
|
80
|
+
def auto_option(name, usage, &block)
|
81
|
+
add_option Clin::Option.parse(name, usage, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add a new option.
|
85
|
+
# @param option [Clin::Option] option to add.
|
86
|
+
def add_option(option)
|
87
|
+
# Need to use += instead of << otherwise the parent class will also be changed
|
88
|
+
@options << option
|
89
|
+
end
|
90
|
+
|
91
|
+
# Add a general option
|
92
|
+
# @param option_cls [Class<GeneralOption>] Class inherited from GeneralOption
|
93
|
+
# @param config [Hash] General option config. Check the general option config.
|
94
|
+
def general_option(option_cls, config = {})
|
95
|
+
option_cls = option_cls.constantize if option_cls.is_a? String
|
96
|
+
@general_options[option_cls] = option_cls.new(config)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Remove a general option
|
100
|
+
# Might be useful if a parent added the option but is not needed in this child.
|
101
|
+
def remove_general_option(option_cls)
|
102
|
+
option_cls = option_cls.constantize if option_cls.is_a? String
|
103
|
+
@general_options.delete(option_cls)
|
104
|
+
end
|
105
|
+
|
106
|
+
# To be called inside OptionParser block
|
107
|
+
# Extract the option in the command line using the OptionParser and map it to the out map.
|
108
|
+
# @param opts [OptionParser]
|
109
|
+
# @param out [Hash] Where the options shall be extracted
|
110
|
+
def register_options(opts, out)
|
111
|
+
@options.each do |option|
|
112
|
+
option.register(opts, out)
|
113
|
+
end
|
114
|
+
|
115
|
+
@general_options.each do |_cls, option|
|
116
|
+
option.class.register_options(opts, out)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Call #execute on each of the general options.
|
121
|
+
# This is called during the command initialization
|
122
|
+
# e.g. A verbose general option execute would be:
|
123
|
+
# ```
|
124
|
+
# def execute(params)
|
125
|
+
# MyApp.verbose = true if params[:verbose]
|
126
|
+
# end
|
127
|
+
# ```
|
128
|
+
def execute_general_options(options)
|
129
|
+
general_options.each do |_cls, gopts|
|
130
|
+
gopts.execute(options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/clin/general_option.rb
CHANGED
data/lib/clin/option.rb
CHANGED
@@ -3,8 +3,31 @@ require 'clin'
|
|
3
3
|
# Option container.
|
4
4
|
# Prefer the `.option`, `.flag_option`,... class methods than `.add_option Option.new(...)`
|
5
5
|
class Clin::Option
|
6
|
+
def self.parse(name, usage, &block)
|
7
|
+
long = nil
|
8
|
+
short = nil
|
9
|
+
argument = nil
|
10
|
+
desc = []
|
11
|
+
usage.split.each do |segment|
|
12
|
+
if segment.start_with? '--'
|
13
|
+
long, argument = segment.split('=', 2)
|
14
|
+
elsif segment.start_with? '-'
|
15
|
+
short = segment
|
16
|
+
else
|
17
|
+
desc << segment
|
18
|
+
end
|
19
|
+
end
|
20
|
+
argument = false if argument.nil?
|
21
|
+
new(name, desc.join(' '), short: short, long: long, argument: argument, &block)
|
22
|
+
end
|
23
|
+
|
6
24
|
attr_accessor :name, :description, :optional_argument, :block, :type, :default
|
7
|
-
|
25
|
+
|
26
|
+
# Set the short name(e.g. -v for verbose)
|
27
|
+
attr_writer :short
|
28
|
+
|
29
|
+
# Set the long name(e.g. --verbose for verbose)
|
30
|
+
attr_writer :long
|
8
31
|
|
9
32
|
# Create a new option.
|
10
33
|
# @param name [String] Option name.
|
@@ -24,7 +47,7 @@ class Clin::Option
|
|
24
47
|
@short = short
|
25
48
|
@long = long
|
26
49
|
@optional_argument = argument_optional
|
27
|
-
|
50
|
+
self.argument = argument
|
28
51
|
@type = type
|
29
52
|
@block = block
|
30
53
|
@default = default
|
@@ -101,6 +124,18 @@ class Clin::Option
|
|
101
124
|
@argument ||= default_argument
|
102
125
|
end
|
103
126
|
|
127
|
+
# Set the argument
|
128
|
+
# @param value
|
129
|
+
def argument=(value)
|
130
|
+
if value
|
131
|
+
arg = Clin::Argument.new(value)
|
132
|
+
@optional_argument = true if arg.optional
|
133
|
+
@argument = arg.name
|
134
|
+
else # If false or nil
|
135
|
+
@argument = value
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
104
139
|
def option_parser_arguments
|
105
140
|
args = [short, long_argument, @type, description]
|
106
141
|
args.compact
|
@@ -153,4 +188,3 @@ class Clin::Option
|
|
153
188
|
out
|
154
189
|
end
|
155
190
|
end
|
156
|
-
|
data/lib/clin/option_list.rb
CHANGED
data/lib/clin/shell.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Class the offer helper method to interact with the user using the command line
|
4
|
+
class Clin::Shell
|
5
|
+
# Input stream, default: STDIN
|
6
|
+
attr_accessor :in
|
7
|
+
|
8
|
+
# Output stream, default: STDOUT
|
9
|
+
attr_accessor :out
|
10
|
+
|
11
|
+
def initialize(input: STDIN, output: STDOUT)
|
12
|
+
@in = input
|
13
|
+
@out = output
|
14
|
+
@yes_or_no_persist = false
|
15
|
+
@override_persist = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Ask a question
|
19
|
+
# @param statement [String]
|
20
|
+
# @param default [String]
|
21
|
+
# @param autocomplete [Array|Proc] Filter for autocomplete
|
22
|
+
def ask(statement, default: nil, autocomplete: nil)
|
23
|
+
answer = scan(statement, autocomplete: autocomplete)
|
24
|
+
if answer.blank?
|
25
|
+
default
|
26
|
+
else
|
27
|
+
answer.strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Ask a question and expect the result to be in the list of choices
|
32
|
+
# Will continue asking until the input is correct
|
33
|
+
# or if a default value is supplied then empty will return.
|
34
|
+
# @param statement [String] Question to ask
|
35
|
+
# @param choices [Array] List of choices
|
36
|
+
# @param default [String] Default value if the user put blank value.
|
37
|
+
# @param allow_initials [Boolean] Allow the user to reply with only the initial of the choice.
|
38
|
+
# (e.g. yes/no => y/n)
|
39
|
+
# If multiple choices start with the same initial
|
40
|
+
# ONLY the first one will be able to be selected using its initial
|
41
|
+
def choose(statement, choices, default: nil, allow_initials: false)
|
42
|
+
Clin::ShellInteraction::Choose.new(self).run(statement, choices,
|
43
|
+
default: default, allow_initials: allow_initials)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Expect the user the return yes or no(y/n also works)
|
47
|
+
# @param statement [String] Question to ask
|
48
|
+
# @param default [String] Default value(yes/no)
|
49
|
+
# @param persist [Boolean] Add "always" to the choices. When all is selected all the following
|
50
|
+
# call to yes_or_no with persist: true will return true instead of asking the user.
|
51
|
+
def yes_or_no(statement, default: nil, persist: false)
|
52
|
+
Clin::ShellInteraction::YesOrNo.new(self).run(statement, default: default, persist: persist)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Yes or no question defaulted to yes
|
56
|
+
# @param options [Hash] Named parameters for yes_or_no
|
57
|
+
# @see #yes_or_no
|
58
|
+
def yes?(statement, options = {})
|
59
|
+
options[:default] = :yes
|
60
|
+
yes_or_no(statement, **options)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Yes or no question defaulted to no
|
64
|
+
# @param options [Hash] Named parameters for yes_or_no
|
65
|
+
# @see #yes_or_no
|
66
|
+
def no?(statement, options = {})
|
67
|
+
options[:default] = :no
|
68
|
+
yes_or_no(statement, **options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# File conflict helper method.
|
72
|
+
# Give the following options to the user
|
73
|
+
# - yes, Yes for this one
|
74
|
+
# - no, No for this one
|
75
|
+
# - all, Yes for all one
|
76
|
+
# - quit, Quit the program
|
77
|
+
# - diff, Diff the 2 files
|
78
|
+
# @param filename [String] Filename with the conflict
|
79
|
+
# @param block [Block] optional block that give the new content in case of diff
|
80
|
+
# @return [Boolean] If the file should be overwritten.
|
81
|
+
def file_conflict(filename, default: nil, &block)
|
82
|
+
Clin::ShellInteraction::FileConflict.new(self).run(filename, default: default, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# File conflict question defaulted to yes
|
86
|
+
def overwrite?(filename, &block)
|
87
|
+
file_conflict(filename, default: :yes, &block)
|
88
|
+
end
|
89
|
+
|
90
|
+
# File conflict question defaulted to no
|
91
|
+
def keep?(filename, &block)
|
92
|
+
file_conflict(filename, default: :no, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Prompt the statement to the user and return his reply.
|
96
|
+
# @param statement [String]
|
97
|
+
# @param autocomplete [Array|Block]
|
98
|
+
protected def scan(statement, autocomplete: nil)
|
99
|
+
unless autocomplete.nil?
|
100
|
+
Readline.completion_proc = if autocomplete.is_a? Proc
|
101
|
+
autocomplete
|
102
|
+
else
|
103
|
+
proc { |s| autocomplete.grep(/^#{Regexp.escape(s)}/) }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
Readline.completion_append_character = nil
|
107
|
+
Readline.readline(statement + ' ', true)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
require 'clin/shell_interaction'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Handle a choose question
|
4
|
+
class Clin::ShellInteraction::Choose < Clin::ShellInteraction
|
5
|
+
def run(statement, choices, default: nil, allow_initials: false)
|
6
|
+
choices = convert_choices(choices)
|
7
|
+
question = prepare_question(statement, choices, default: default, initials: allow_initials)
|
8
|
+
loop do
|
9
|
+
answer = @shell.ask(question, default: default, autocomplete: choices.keys)
|
10
|
+
unless answer.nil?
|
11
|
+
choices.each do |choice, _|
|
12
|
+
if choice.casecmp(answer) == 0 || (allow_initials && choice[0].casecmp(answer[0]) == 0)
|
13
|
+
return choice
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
print_choices_help(choices, allow_initials: allow_initials)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected def choice_message(choices, default: nil, initials: false)
|
22
|
+
choices = choices.keys.map { |x| x == default ? x.to_s.upcase : x }
|
23
|
+
msg = if initials
|
24
|
+
choices.map { |x| x[0] }.join('')
|
25
|
+
else
|
26
|
+
choices.join(',')
|
27
|
+
end
|
28
|
+
"[#{msg}]"
|
29
|
+
end
|
30
|
+
|
31
|
+
protected def prepare_question(statement, choices, default: nil, initials: false)
|
32
|
+
"#{statement} #{choice_message(choices, default: default, initials: initials)}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Convert the choices to a hash with key being the choice and value the description
|
36
|
+
protected def convert_choices(choices)
|
37
|
+
if choices.is_a? Array
|
38
|
+
Hash[*choices.map { |k| [k, ''] }.flatten]
|
39
|
+
elsif choices.is_a? Hash
|
40
|
+
choices
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected def print_choices_help(choices, allow_initials: false)
|
45
|
+
puts 'Choose from:'
|
46
|
+
used_initials = Set.new
|
47
|
+
choices.each do |choice, description|
|
48
|
+
suf = choice.to_s
|
49
|
+
suf += ", #{description}" unless description.blank?
|
50
|
+
line = if allow_initials && !used_initials.include?(choice[0])
|
51
|
+
used_initials << choice[0]
|
52
|
+
" #{choice[0]} - #{suf}"
|
53
|
+
else
|
54
|
+
" #{suf}"
|
55
|
+
end
|
56
|
+
puts line
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Handle the file_conflict interaction with the user.
|
4
|
+
class Clin::ShellInteraction::FileConflict < Clin::ShellInteraction
|
5
|
+
def run(filename, default: nil, &block)
|
6
|
+
choices = file_conflict_choices
|
7
|
+
choices = choices.except(:diff) unless block_given?
|
8
|
+
return true if persist?
|
9
|
+
result = nil
|
10
|
+
while result.nil?
|
11
|
+
choice = @shell.choose("Overwrite '#{filename}'?", choices,
|
12
|
+
default: default, allow_initials: true)
|
13
|
+
result = handle_choice(choice, filename, &block)
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
protected def handle_choice(choice, filename, &block)
|
19
|
+
case choice
|
20
|
+
when :yes
|
21
|
+
return true
|
22
|
+
when :no
|
23
|
+
return false
|
24
|
+
when :always
|
25
|
+
return persist!
|
26
|
+
when :quit
|
27
|
+
puts 'Aborting...'
|
28
|
+
fail SystemExit
|
29
|
+
when :diff
|
30
|
+
show_diff(filename, block.call)
|
31
|
+
return nil
|
32
|
+
else
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected def file_conflict_choices
|
38
|
+
{yes: 'Overwrite',
|
39
|
+
no: 'Do not Overwrite',
|
40
|
+
always: 'Override this and all the next',
|
41
|
+
quit: 'Abort!',
|
42
|
+
diff: 'Show the difference',
|
43
|
+
help: 'Show this'}
|
44
|
+
end
|
45
|
+
|
46
|
+
protected def show_diff(old_file, new_content)
|
47
|
+
Tempfile.open(File.basename(old_file)) do |f|
|
48
|
+
f.write new_content
|
49
|
+
f.rewind
|
50
|
+
system %(#{Clin.diff_cmd} "#{old_file}" "#{f.path}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Handle a simple yes/no interaction
|
4
|
+
class Clin::ShellInteraction::YesOrNo < Clin::ShellInteraction
|
5
|
+
def run(statement, default: nil, persist: false)
|
6
|
+
default = default.to_sym unless default.nil?
|
7
|
+
options = [:yes, :no]
|
8
|
+
if persist
|
9
|
+
return true if persist?
|
10
|
+
options << :always
|
11
|
+
end
|
12
|
+
choice = @shell.choose(statement, options, default: default, allow_initials: true)
|
13
|
+
return persist! if choice == :always
|
14
|
+
choice == :yes
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'clin'
|
2
|
+
|
3
|
+
# Parent class for shell interaction.
|
4
|
+
class Clin::ShellInteraction
|
5
|
+
class << self
|
6
|
+
attr_accessor :persist
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :shell
|
10
|
+
|
11
|
+
# @param shell [Clin::Shell] Shell starting the interaction.
|
12
|
+
def initialize(shell)
|
13
|
+
@shell = shell
|
14
|
+
self.class.persist ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Boolean]
|
18
|
+
def persist?
|
19
|
+
self.class.persist[@shell] ||= false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Mark the current shell to persist file interaction
|
23
|
+
def persist!
|
24
|
+
self.class.persist[@shell] = persist_answer
|
25
|
+
end
|
26
|
+
|
27
|
+
def persist_answer
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'clin/shell_interaction/file_conflict'
|
33
|
+
require 'clin/shell_interaction/yes_or_no'
|
34
|
+
require 'clin/shell_interaction/choose'
|
data/lib/clin/version.rb
CHANGED
data/lib/clin.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/core_ext'
|
3
3
|
require 'optparse'
|
4
|
+
require 'readline'
|
4
5
|
require 'clin/version'
|
5
6
|
|
6
7
|
# Clin Global module. All classes and clin modules should be inside this module
|
@@ -9,6 +10,9 @@ module Clin
|
|
9
10
|
# Set the global exe name. `Clin.exe_name = 'git'`
|
10
11
|
attr_writer :exe_name
|
11
12
|
|
13
|
+
# Set the command when comparing 2 files(Used in the shell)
|
14
|
+
attr_writer :diff_cmd
|
15
|
+
|
12
16
|
def default_exe_name
|
13
17
|
'command'
|
14
18
|
end
|
@@ -18,15 +22,20 @@ module Clin
|
|
18
22
|
def exe_name
|
19
23
|
@exe_name ||= Clin.default_exe_name
|
20
24
|
end
|
25
|
+
|
26
|
+
def diff_cmd
|
27
|
+
@diff_cmd ||= 'diff -u'
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
32
|
+
require 'clin/command_mixin'
|
24
33
|
require 'clin/command'
|
25
34
|
require 'clin/command_parser'
|
26
|
-
require 'clin/command_options_mixin'
|
27
35
|
require 'clin/general_option'
|
28
36
|
require 'clin/command_dispatcher'
|
29
37
|
require 'clin/common/help_options'
|
30
38
|
require 'clin/errors'
|
31
39
|
require 'clin/option'
|
32
40
|
require 'clin/option_list'
|
41
|
+
require 'clin/shell'
|
@@ -43,8 +43,8 @@ RSpec.describe Clin::CommandDispatcher do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
describe '#parse' do
|
46
|
-
let(:cmd1) { double(:
|
47
|
-
let(:cmd2) { double(:
|
46
|
+
let(:cmd1) { double(:command_mixin, parse: 'cmd1', usage: 'cmd1 use') }
|
47
|
+
let(:cmd2) { double(:command_mixin, parse: 'cmd2', usage: 'cmd1 use') }
|
48
48
|
let(:args) { %w(some args) }
|
49
49
|
subject { Clin::CommandDispatcher.new(cmd1, cmd2) }
|
50
50
|
context 'when first command match' do
|
@@ -81,4 +81,4 @@ RSpec.describe Clin::CommandDispatcher do
|
|
81
81
|
it { expect(@error.to_s).to eq(subject.help_message) }
|
82
82
|
end
|
83
83
|
end
|
84
|
-
end
|
84
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec.describe Clin::CommandMixin::Core do
|
2
|
+
def new_subject
|
3
|
+
a = Class.new
|
4
|
+
a.include Clin::CommandMixin::Core
|
5
|
+
a
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.prioritize' do
|
9
|
+
subject { new_subject }
|
10
|
+
it 'set priority to 1 when no argument given' do
|
11
|
+
subject.prioritize
|
12
|
+
expect(subject._priority).to be 1
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'set priority to 1 when no argument given' do
|
16
|
+
subject.prioritize(42)
|
17
|
+
expect(subject._priority).to be 42
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '.priority' do
|
22
|
+
subject { new_subject }
|
23
|
+
it 'get the default priority' do
|
24
|
+
expect(subject.priority).to be subject._default_priority
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'sum default and priority when subject has been prioritize' do
|
28
|
+
subject.prioritize(42)
|
29
|
+
expect(subject.priority).to be subject._default_priority + 42
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has a smaller priority when inheriting' do
|
33
|
+
child = Class.new(subject)
|
34
|
+
expect(child.priority).to be < subject.priority
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'clin/command_options_mixin'
|
3
2
|
|
4
|
-
RSpec.describe Clin::
|
3
|
+
RSpec.describe Clin::CommandMixin::Options do
|
4
|
+
def new_subject
|
5
|
+
a = Class.new
|
6
|
+
a.include Clin::CommandMixin::Options
|
7
|
+
a
|
8
|
+
end
|
9
|
+
|
5
10
|
describe '#add_option' do
|
6
|
-
subject {
|
11
|
+
subject { new_subject }
|
7
12
|
let(:option) { Clin::Option.new(:name, '-n') }
|
8
13
|
before do
|
9
14
|
subject.add_option option
|
@@ -11,11 +16,10 @@ RSpec.describe Clin::CommandOptionsMixin do
|
|
11
16
|
|
12
17
|
it { expect(subject.options.size).to be 1 }
|
13
18
|
it { expect(subject.options.first).to eq option }
|
14
|
-
it { expect(Clin::CommandOptionsMixin.options.size).to be 0 }
|
15
19
|
end
|
16
20
|
|
17
21
|
describe '#option' do
|
18
|
-
subject {
|
22
|
+
subject { new_subject }
|
19
23
|
let(:args) { [:name, '-n'] }
|
20
24
|
let(:option) { Clin::Option.new(*args) }
|
21
25
|
|
@@ -29,7 +33,7 @@ RSpec.describe Clin::CommandOptionsMixin do
|
|
29
33
|
end
|
30
34
|
|
31
35
|
describe '#general_option' do
|
32
|
-
subject {
|
36
|
+
subject { new_subject }
|
33
37
|
let(:option) { double(:option, register_options: true, new: true) }
|
34
38
|
before do
|
35
39
|
subject.general_option option
|
@@ -37,11 +41,10 @@ RSpec.describe Clin::CommandOptionsMixin do
|
|
37
41
|
it { expect(option).to have_received(:new) }
|
38
42
|
it { expect(subject.general_options.size).to be 1 }
|
39
43
|
it { expect(subject.general_options.values.first).to eq(true) }
|
40
|
-
it { expect(Clin::CommandOptionsMixin.general_options.size).to be 0 }
|
41
44
|
end
|
42
45
|
|
43
46
|
describe '#register_options' do
|
44
|
-
subject {
|
47
|
+
subject { new_subject }
|
45
48
|
let(:opt1) { double(:option, register: true) }
|
46
49
|
let(:opt2) { double(:option, register: true) }
|
47
50
|
let(:g_opt_cls) { double(:general_option_class, register_options: true) }
|
@@ -130,8 +130,8 @@ RSpec.describe Clin::CommandParser do
|
|
130
130
|
end
|
131
131
|
|
132
132
|
context 'when using commands' do
|
133
|
-
let(:cmd1) { double(:
|
134
|
-
let(:cmd2) { double(:
|
133
|
+
let(:cmd1) { double(:command_mixin) }
|
134
|
+
let(:cmd2) { double(:command_mixin) }
|
135
135
|
before do
|
136
136
|
@command.dispatch :args, commands: [cmd1, cmd2]
|
137
137
|
allow_any_instance_of(Clin::CommandDispatcher).to receive(:initialize)
|
data/spec/clin/command_spec.rb
CHANGED
@@ -67,9 +67,9 @@ RSpec.describe Clin::Command do
|
|
67
67
|
subject.arguments(%w(remote <args>...))
|
68
68
|
end
|
69
69
|
|
70
|
-
let(:cmd1) { double(:
|
71
|
-
let(:cmd2) { double(:
|
72
|
-
let(:cmd3) { double(:
|
70
|
+
let(:cmd1) { double(:command_mixin, usage: 'cmd1') }
|
71
|
+
let(:cmd2) { double(:command_mixin, usage: 'cmd2') }
|
72
|
+
let(:cmd3) { double(:command_mixin, usage: 'cmd3') }
|
73
73
|
let(:cmds) { [cmd1, cmd2, cmd3] }
|
74
74
|
let(:opts) { double(:option_parser, separator: true) }
|
75
75
|
before do
|