clin 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.lint-ci.yml +2 -0
  3. data/.simplecov +5 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +11 -0
  6. data/README.md +5 -4
  7. data/benchmarks/bench.rb +21 -0
  8. data/benchmarks/text_bench.rb +78 -0
  9. data/clin.gemspec +2 -1
  10. data/examples/reusable_options.rb +19 -0
  11. data/examples/simple.rb +8 -3
  12. data/examples/test.rb +5 -5
  13. data/examples/text_builder.rb +40 -0
  14. data/lib/clin/argument.rb +19 -2
  15. data/lib/clin/command_mixin/core.rb +13 -18
  16. data/lib/clin/command_mixin/options.rb +37 -26
  17. data/lib/clin/command_parser.rb +46 -57
  18. data/lib/clin/common/help_options.rb +1 -0
  19. data/lib/clin/errors.rb +50 -4
  20. data/lib/clin/line_reader/basic.rb +38 -0
  21. data/lib/clin/line_reader/readline.rb +53 -0
  22. data/lib/clin/line_reader.rb +16 -0
  23. data/lib/clin/option.rb +24 -11
  24. data/lib/clin/option_parser.rb +159 -0
  25. data/lib/clin/shell.rb +36 -15
  26. data/lib/clin/shell_interaction/choose.rb +19 -11
  27. data/lib/clin/shell_interaction/file_conflict.rb +4 -1
  28. data/lib/clin/shell_interaction/select.rb +44 -0
  29. data/lib/clin/shell_interaction.rb +1 -0
  30. data/lib/clin/text/table.rb +270 -0
  31. data/lib/clin/text.rb +152 -0
  32. data/lib/clin/version.rb +1 -1
  33. data/lib/clin.rb +10 -1
  34. data/spec/clin/command_dispacher_spec.rb +1 -1
  35. data/spec/clin/command_mixin/options_spec.rb +38 -15
  36. data/spec/clin/command_parser_spec.rb +27 -51
  37. data/spec/clin/line_reader/basic_spec.rb +54 -0
  38. data/spec/clin/line_reader/readline_spec.rb +64 -0
  39. data/spec/clin/line_reader_spec.rb +17 -0
  40. data/spec/clin/option_parser_spec.rb +217 -0
  41. data/spec/clin/option_spec.rb +5 -7
  42. data/spec/clin/shell_interaction/choose_spec.rb +30 -0
  43. data/spec/clin/shell_interaction/file_interaction_spec.rb +18 -0
  44. data/spec/clin/shell_interaction/select_spec.rb +96 -0
  45. data/spec/clin/shell_spec.rb +42 -0
  46. data/spec/clin/text/table_cell_spec.rb +72 -0
  47. data/spec/clin/text/table_row_spec.rb +74 -0
  48. data/spec/clin/text/table_separator_row_spec.rb +82 -0
  49. data/spec/clin/text/table_spec.rb +259 -0
  50. data/spec/clin/text_spec.rb +158 -0
  51. data/spec/examples/list_option_spec.rb +6 -2
  52. data/spec/examples/reusable_options_spec.rb +21 -0
  53. data/spec/examples/simple_spec.rb +9 -9
  54. data/spec/spec_helper.rb +3 -2
  55. metadata +54 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be4386269225e68a69c419a0a4a90668f5da8287
4
- data.tar.gz: 0a33c686511f6a0498fe40276c936b6574fbe497
3
+ metadata.gz: 398c6c91b940d2efda70f9879e2743ae5c16c33c
4
+ data.tar.gz: b36c46118f12f7322ced5a53fe519f0402a45a19
5
5
  SHA512:
6
- metadata.gz: 2878f163e2974a9a3c93e9f565e9b5fdaf5ec91bcced8856b95b7045f45eca8427da8a22631f1741da582982ab1c575e19c6115cee4dd8078a69a08f217cf41d
7
- data.tar.gz: eba57a52877c4ea5fca71571ab1a534459f173d9715028774b76a6be8fb9e7721b077fdc8371839ba770a43dfde17bd34fd7bf2c71e2001e88c6898be7458fab
6
+ metadata.gz: 9759baa328947f0dbf9247f0e96997f6e060c28bb35dbd98b18daa3aff46b928f595fb80fa34b9fd7072fad59cd1b186540fdadb81abed0e7b5a3114ebfec28f
7
+ data.tar.gz: 5fcd4a5a2a05a86488d65b62737e21d87b1c8878a0c4306f6b855c78319bc0cede1d0b2de75df313913607991f695a794be703838373596e558410462725778f
data/.lint-ci.yml ADDED
@@ -0,0 +1,2 @@
1
+ linters:
2
+ - rubocop
data/.simplecov ADDED
@@ -0,0 +1,5 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start do
4
+ add_filter '/examples/'
5
+ end
data/.travis.yml CHANGED
@@ -9,5 +9,13 @@ gemfile:
9
9
 
10
10
  matrix:
11
11
 
12
+ after_success:
13
+ - ruby benchmarks/bench.rb
14
+
12
15
  notifications:
13
16
  email: false
17
+
18
+
19
+ addons:
20
+ code_climate:
21
+ repo_token: 71237749c90394bd3625b66744eaa59ff237b291cef608fba8d11474fe9e8a5b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.4.0
2
+ Features:
3
+ - Command line parsing is now done internally, i.e. removed optparse (#6).
4
+ - Added a text builder interface.
5
+ - Added a table builder interface.
6
+ - Shell interact with text builder interface
7
+ - Shell#say, Shell#indent, Shell#password
8
+
9
+ Bug fix:
10
+ - OptParse was hijacking the -v --version.
11
+
1
12
  ## 0.3.0
2
13
  Features:
3
14
  - Added a shell class for any user interaction.
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Clin
2
- [![Build Status](https://travis-ci.org/timcolonel/clin.svg?branch=master)](https://travis-ci.org/timcolonel/clin)
3
- [![Coverage Status](https://coveralls.io/repos/timcolonel/clin/badge.svg?branch=master)](https://coveralls.io/r/timcolonel/clin?branch=master)
4
- [![Code Climate](https://codeclimate.com/github/timcolonel/clin/badges/gpa.svg)](https://codeclimate.com/github/timcolonel/clin)
5
- [![Inline docs](http://inch-ci.org/github/timcolonel/clin.svg?branch=master)](http://inch-ci.org/github/timcolonel/clin)
2
+ [![Gem](https://img.shields.io/gem/v/clin.svg?style=flat-square)](http://rubygems.org/gems/clin)
3
+ [![Build Status](https://img.shields.io/travis/timcolonel/clin/master.svg?style=flat-square)](https://travis-ci.org/timcolonel/clin)
4
+ [![Code Climate](https://img.shields.io/codeclimate/coverage/github/timcolonel/clin.svg?style=flat-square)](https://codeclimate.com/github/timcolonel/clin/coverage)
5
+ [![Test Coverage](https://img.shields.io/codeclimate/github/timcolonel/clin.svg?style=flat-square)](https://codeclimate.com/github/timcolonel/clin)
6
+ [![Inline docs](http://inch-ci.org/github/timcolonel/clin.svg?branch=master&style=flat-square)](http://inch-ci.org/github/timcolonel/clin)
6
7
 
7
8
  Clin is Command Line Interface library that provide an clean api for complex command configuration.
8
9
  The way Clin is design allow a command defined by the user to be called via the command line as well as directly in the code without any additional configuration
@@ -0,0 +1,21 @@
1
+ require 'benchmark/ips'
2
+ $LOAD_PATH.push File.expand_path('../..', __FILE__)
3
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
4
+ require 'clin'
5
+
6
+ Benchmark.ips do |x|
7
+ x.report('simple') do
8
+ require 'examples/simple'
9
+ SimpleCommand.parse('display Some -e "Even More"')
10
+ end
11
+
12
+ x.report('auto_option') do
13
+ require 'examples/auto_option'
14
+ AutoOptionCommand.parse('--eko="Lorem ipsum"')
15
+ end
16
+
17
+ x.report('nested_dispatcher') do
18
+ require 'examples/nested_dispatcher'
19
+ DispatchCommand.parse('you display Some --verbose -e More --times 3')
20
+ end
21
+ end
@@ -0,0 +1,78 @@
1
+ require 'benchmark/ips'
2
+ $LOAD_PATH.push File.expand_path('../..', __FILE__)
3
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
4
+ require 'clin'
5
+
6
+ values = ['Some value 1', 'Some value 2']
7
+ Benchmark.ips do |x|
8
+ x.report('Normal <<') do
9
+ t = ''
10
+ t << 'Usage:' << "\n"
11
+ t << ' display Message' << "\n"
12
+ t << ' print Message' << "\n"
13
+ t << ' --echo' << "\n"
14
+ t << ' --verbose' << "\n"
15
+ t << 'Description: ' << "\n"
16
+ t << ' This is a description' << "\n"
17
+ t << '' << "\n"
18
+ t << 'Examples:' << "\n"
19
+ t << 'Examples:' << "\n"
20
+ t << ' -display Message' << "\n"
21
+ t << ' -print Message' << "\n"
22
+ t << 'Values: ' << "\n"
23
+ values.each do |val|
24
+ t << "***#{val}" << "\n"
25
+ end
26
+ t = 'You have an error!' + "\n" << t
27
+ end
28
+
29
+ x.report('Normal +=') do
30
+ t = ''
31
+ t += 'Usage:' + "\n"
32
+ t += ' display Message' + "\n"
33
+ t += ' print Message' + "\n"
34
+ t += ' --echo' + "\n"
35
+ t += ' --verbose' + "\n"
36
+ t += 'Description: ' + "\n"
37
+ t += ' This is a description' + "\n"
38
+ t += '' + "\n"
39
+ t += 'Examples:' + "\n"
40
+ t += 'Examples:' + "\n"
41
+ t += ' -display Message' + "\n"
42
+ t += ' -print Message' + "\n"
43
+ t += 'Values: ' + "\n"
44
+ values.each do |val|
45
+ t += "***#{val}" + "\n"
46
+ end
47
+ t = 'You have an error!' + "\n" + t
48
+ end
49
+
50
+ x.report('Text builder') do
51
+ Clin::Text.new do |t|
52
+ t.line 'Usage:'
53
+ t.indent ' ' do
54
+ t.line 'display Message'
55
+ t.line 'print Message'
56
+
57
+ t.indent ' ' do
58
+ t.line '--echo'
59
+ t.line '--verbose'
60
+ end
61
+ end
62
+ t.line 'Description: '
63
+ t.line 'This is a description', indent: ' '
64
+ t.blank
65
+ t.line 'Examples:'
66
+ t.indent ' -' do
67
+ t.line 'display Message'
68
+ t.line 'print Message'
69
+ end
70
+ t.line 'Values: '
71
+ t.lines values, indent: '*** '
72
+
73
+ t.prefix 'You have an error!'
74
+ end.to_s
75
+ end
76
+
77
+ x.compare!
78
+ end
data/clin.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'bundler', '~> 1.7'
23
23
  spec.add_development_dependency 'rake', '~> 10.0'
24
24
  spec.add_development_dependency 'rspec', '>= 3.0'
25
- spec.add_development_dependency 'coveralls'
25
+ spec.add_development_dependency 'codeclimate-test-reporter'
26
26
  spec.add_development_dependency 'faker'
27
+ spec.add_development_dependency 'benchmark-ips'
27
28
  end
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
+ require 'clin'
3
+
4
+ # Reusable option definition
5
+ class SourceOptions < Clin::GeneralOption
6
+ option :source, 'Set the source'
7
+ end
8
+
9
+ # Command Using reusable option
10
+ class ReusableOptionCommand < Clin::Command
11
+ flag_option :verbose, 'Verbose'
12
+ option :echo, 'Echo'
13
+
14
+ general_option SourceOptions
15
+
16
+ def run
17
+ puts params
18
+ end
19
+ end
data/examples/simple.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
2
  require 'clin'
3
- require 'clin'
4
3
 
5
4
  # Simple command Example
6
5
  class SimpleCommand < Clin::Command
@@ -9,6 +8,8 @@ class SimpleCommand < Clin::Command
9
8
  option :echo, 'Echo some text'
10
9
  general_option Clin::HelpOptions
11
10
 
11
+ description 'Simple command that print stuff!'
12
+
12
13
  def run
13
14
  puts @params[:message]
14
15
  puts @params[:echo]
@@ -16,5 +17,9 @@ class SimpleCommand < Clin::Command
16
17
  end
17
18
 
18
19
  # Run example:
19
- # SimpleCommand.parse('display "My Message" --echo SOME').run
20
- # SimpleCommand.parse('').run
20
+ # SimpleCommand.parse('display "My Message" -e SOME').run
21
+ begin
22
+ SimpleCommand.parse('').run
23
+ rescue Clin::HelpError => e
24
+ puts e
25
+ end
data/examples/test.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
2
  require 'clin'
3
-
3
+ require 'io/console'
4
4
 
5
5
  shell = Clin::Shell.new
6
-
7
- choice = shell.choose('What?', %w(france usa italy germany))
8
-
9
- puts 'YOu ' + choice
6
+ 100.times.each do |i|
7
+ shell.say "# #{i}", indent: i
8
+ sleep(0.5)
9
+ end
@@ -0,0 +1,40 @@
1
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
+ require 'clin'
3
+ values = ['Some value 1', 'Some value 2']
4
+ text = Clin::Text.new do |t|
5
+ t.line 'Usage:'
6
+ t.indent 2 do
7
+ t.line 'display Message'
8
+ t.line 'print Message'
9
+
10
+ t.indent 3 do
11
+ t.line '--echo'
12
+ t.line '--verbose'
13
+ end
14
+ end
15
+ t.line 'Description: '
16
+ t.line 'This is a description', indent: 3
17
+ t.blank
18
+ t.line 'Examples:'
19
+ t.indent ' -' do
20
+ t.line 'display Message'
21
+ t.line 'print Message'
22
+ end
23
+ t.line 'Values: '
24
+ t.lines values, indent: '*** '
25
+ end
26
+ puts text
27
+
28
+ table = Clin::Text::Table.new(border: true) do |t|
29
+ t.align :right, :center, :left
30
+ # t.column_delimiter ' - ', ' # '
31
+ t.header %w(First Last Email)
32
+
33
+ t.row %w(Timothee Guerin timothee.guerin@outlook.com)
34
+ t.row %w(Some Guy Some.Guy@outlook.com)
35
+
36
+ t.separator
37
+
38
+ t.row %w(VeryLongFirstName Guy Some.Other@outlook.com)
39
+ end
40
+ puts table
data/lib/clin/argument.rb CHANGED
@@ -2,12 +2,29 @@ require 'clin'
2
2
 
3
3
  # Command line positional argument(not option)
4
4
  class Clin::Argument
5
+ # Original name specified in the command
5
6
  attr_accessor :original
7
+
8
+ # If the argument is optional
6
9
  attr_accessor :optional
10
+
11
+ # If the argument accept multiple values
7
12
  attr_accessor :multiple
13
+
14
+ # If the argument is a fixed argument(User value need to match the name)
8
15
  attr_accessor :variable
16
+
17
+ # The name extracted without the brackets and arrows.
18
+ # This will be the key in the params when initializing a command
9
19
  attr_accessor :name
10
20
 
21
+ # Create a new argument from string
22
+ # +argument+ will be used to deduce the name, if it's fixed, optional, accept multiple values
23
+ # If the argument is a simple string(e.g. install) then it will be a fixed argument
24
+ # For the argument to accept variable values it must be surrounded with <> (e.g. <command>)
25
+ # For the argument to be optional it must be surrounded with [] (e.g. [<value>])
26
+ # For the argument to accept multiple value it must be suffixed with ... (e.g. <commands>...)
27
+ # @param argument [String] argument Value
11
28
  def initialize(argument)
12
29
  @original = argument
13
30
  @optional = false
@@ -63,7 +80,7 @@ class Clin::Argument
63
80
  def ensure_fixed(args)
64
81
  [*args].each do |arg|
65
82
  next if arg == @name
66
- fail Clin::FixedArgumentError, @name, arg
83
+ fail Clin::RequiredArgumentError, @name, arg
67
84
  end
68
85
  end
69
86
 
@@ -77,7 +94,7 @@ class Clin::Argument
77
94
  if @variable
78
95
  fail Clin::MissingArgumentError, @name
79
96
  else
80
- fail Clin::FixedArgumentError, @name
97
+ fail Clin::RequiredArgumentError, @name
81
98
  end
82
99
  end
83
100
 
@@ -121,24 +121,6 @@ module Clin::CommandMixin::Core
121
121
  @_default_priority + @_priority
122
122
  end
123
123
 
124
- # Build the Option Parser object
125
- # Used to parse the option
126
- # Useful for regenerating the help as well.
127
- def option_parser(out = {})
128
- OptionParser.new do |opts|
129
- opts.banner = banner
130
- opts.separator ''
131
- opts.separator 'Options:'
132
- register_options(opts, out)
133
- dispatch_doc(opts)
134
- unless @description.blank?
135
- opts.separator "\nDescription:"
136
- opts.separator @description
137
- end
138
- opts.separator ''
139
- end
140
- end
141
-
142
124
  def default_commands
143
125
  subcommands.sort_by(&:priority).reverse
144
126
  end
@@ -148,5 +130,18 @@ module Clin::CommandMixin::Core
148
130
  def subcommands
149
131
  subclasses.reject(&:abstract?)
150
132
  end
133
+
134
+ def help
135
+ Clin::Text.new do |t|
136
+ t.line banner
137
+ t.blank
138
+ t.line 'Options:'
139
+ t.text option_help, indent: 2
140
+ unless description.blank?
141
+ t.line 'Description:'
142
+ t.line description, indent: 2
143
+ end
144
+ end
145
+ end
151
146
  end
152
147
  end
@@ -6,38 +6,22 @@ module Clin::CommandMixin::Options
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- self.options = []
9
+ self.specific_options = []
10
10
  self.general_options = {}
11
11
  # Trigger when a class inherit this class
12
12
  # It will clone attributes that need inheritance
13
13
  # @param subclass [Clin::Command]
14
14
  def self.inherited(subclass)
15
- subclass.options = @options.clone
15
+ subclass.specific_options = @specific_options.clone
16
16
  subclass.general_options = @general_options.clone
17
17
  super
18
18
  end
19
19
  end
20
20
 
21
21
  module ClassMethods # :nodoc:
22
- attr_accessor :options
22
+ attr_accessor :specific_options
23
23
  attr_accessor :general_options
24
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
25
  # Add an option.
42
26
  # Helper method that just create a new Clin::Option with the argument then call add_option
43
27
  # ```
@@ -85,7 +69,7 @@ module Clin::CommandMixin::Options
85
69
  # @param option [Clin::Option] option to add.
86
70
  def add_option(option)
87
71
  # Need to use += instead of << otherwise the parent class will also be changed
88
- @options << option
72
+ @specific_options << option
89
73
  end
90
74
 
91
75
  # Add a general option
@@ -105,16 +89,17 @@ module Clin::CommandMixin::Options
105
89
 
106
90
  # To be called inside OptionParser block
107
91
  # 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)
92
+ # @return [Hash] Where the options shall be extracted
93
+ def option_defaults
94
+ out = {}
95
+ @specific_options.each do |option|
96
+ option.load_default(out)
113
97
  end
114
98
 
115
99
  @general_options.each do |_cls, option|
116
- option.class.register_options(opts, out)
100
+ out.merge! option.class.option_defaults
117
101
  end
102
+ out
118
103
  end
119
104
 
120
105
  # Call #execute on each of the general options.
@@ -130,5 +115,31 @@ module Clin::CommandMixin::Options
130
115
  gopts.execute(options)
131
116
  end
132
117
  end
118
+
119
+ # Return all options
120
+ def options
121
+ specific_options + general_options.keys.map(&:options).flatten
122
+ end
123
+
124
+ def find_option(value)
125
+ find_option_by(name: value)
126
+ end
127
+
128
+ def find_option_by(hash)
129
+ key, value = hash.first
130
+ options.find { |x| x.send(key) == value }
131
+ end
132
+
133
+ def option_help
134
+ Clin::Text.new do |t|
135
+ options.each do |option|
136
+ t.line option.banner
137
+ end
138
+
139
+ general_options.each do |cls, _|
140
+ t.text cls.option_help
141
+ end
142
+ end
143
+ end
133
144
  end
134
145
  end
@@ -2,6 +2,9 @@ require 'clin'
2
2
 
3
3
  # Command parser
4
4
  class Clin::CommandParser
5
+ # List of errors that have occurred during the parsing
6
+ attr_reader :errors
7
+
5
8
  # Create the command parser
6
9
  # @param command_cls [Class<Clin::Command>] Command that must be matched
7
10
  # @param argv [Array<String>] List of CL arguments
@@ -11,75 +14,59 @@ class Clin::CommandParser
11
14
  argv = Shellwords.split(argv) if argv.is_a? String
12
15
  @argv = argv
13
16
  @fallback_help = fallback_help
17
+ @options = {}
18
+ @arguments = {}
19
+ @errors = []
20
+ @skipped_options = []
21
+ end
22
+
23
+ def params
24
+ out = @options.merge(@arguments)
25
+ out[:skipped_options] = @skipped_options if @command.skip_options?
26
+ out
27
+ end
28
+
29
+ def init_defaults
30
+ @options = @command.option_defaults
14
31
  end
15
32
 
16
33
  # Parse the command line.
17
34
  def parse
18
35
  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
36
+ init_defaults
37
+ parse_options(argv)
38
+ parse_arguments(argv)
32
39
 
33
- return redispatch(options) if @command.redispatch?
34
- obj = @command.new(options)
35
- handle_error(error)
40
+ return redispatch(params) if @command.redispatch?
41
+ obj = @command.new(params)
42
+ validate!
36
43
  obj
37
44
  end
38
45
 
39
- # Parse the options in the argv.
40
- # @return [Array] the list of argv that are not options(positional arguments)
41
46
  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
47
+ parser = Clin::OptionParser.new(@command, argv)
48
+ @options.merge! parser.parse
49
+ @skipped_options = parser.skipped_options
50
+ @errors += parser.errors
51
+ argv.replace(parser.arguments)
52
+ @options
53
53
  end
54
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
55
+ def add_error(err)
56
+ @errors << err
73
57
  end
74
58
 
75
59
  # Parse the argument. The options must have been strip out first.
76
60
  def parse_arguments(argv)
77
- out = {}
78
61
  @command.args.each do |arg|
79
62
  value, argv = arg.parse(argv)
80
- out[arg.name.to_sym] = value
63
+
64
+ @arguments[arg.name.to_sym] = value
81
65
  end
82
- out.delete_if { |_, v| v.nil? }
66
+ @arguments.delete_if { |_, v| v.nil? }
67
+ @arguments
68
+ rescue Clin::ArgumentError => e
69
+ add_error e
83
70
  end
84
71
 
85
72
  # Method called after the argument have been parsed and before creating the command
@@ -91,7 +78,7 @@ class Clin::CommandParser
91
78
  begin
92
79
  dispatcher.parse(redispatch_arguments(params))
93
80
  rescue Clin::HelpError
94
- raise Clin::HelpError, @command.option_parser
81
+ raise Clin::HelpError, @command
95
82
  end
96
83
  end
97
84
 
@@ -105,11 +92,13 @@ class Clin::CommandParser
105
92
  args
106
93
  end
107
94
 
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
95
+ def valid?
96
+ @errors.empty?
97
+ end
98
+
99
+ def validate!
100
+ return if valid?
101
+ fail Clin::HelpError, @command if @fallback_help
102
+ fail @errors.sort_by { |e| e.class.severity }.last
114
103
  end
115
104
  end
@@ -1,5 +1,6 @@
1
1
  require 'clin'
2
2
  require 'clin/general_option'
3
+
3
4
  # Help option class
4
5
  # Add the help option to you command
5
6
  # ```