clin 0.3.0 → 0.4.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.
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
  # ```