clin 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 98bdce921d4896c86ed482d8db5cf87a74b2795a
4
- data.tar.gz: e025be0b4c3c5fd36f49437e21abfc1f00ef1a0c
3
+ metadata.gz: 96826af1a7b6e82847b69c692292d7c2a10c69ed
4
+ data.tar.gz: 28fd292aafc8be55a10af75853283a4def9def65
5
5
  SHA512:
6
- metadata.gz: 8a70c2a7c6e53bf8eb781b37841dc24052e8396a2b6a2e8d73d6ea9dc90525fe1e03bf2437f43aefdeaf6aef648b210d7c79fec2fd8d7a31734c5ab21da7d9c1
7
- data.tar.gz: ef60cf35207752817b52bae1514c55f5db9c12e5f87ae322a17469b9039b6a37470327c6bb4e4d43d37f024c5ae5acd283ec200090c6bad5f6705739086edd49
6
+ metadata.gz: 3690346888ff69f711f292bca3f1e15a4580599ea12f070264b2c6eb6cdedc3f5081ac8113c08d2f391e1a892c6e6e36832d9351367ce83a924ab9ec801392e3
7
+ data.tar.gz: 05d2c816d4140359ce2a01fca4462417b6c55fca85580fb7ffe7b8692b60498fe33b2dad151b21c783b7673b1a47f8e31a19215e1c63982a072e73a28345f1fd
data/.gitignore CHANGED
@@ -12,4 +12,5 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
- .idea
15
+ .idea
16
+ *.gem
data/.rubocop.yml CHANGED
@@ -13,6 +13,9 @@ Metrics/MethodLength:
13
13
  CountComments: false
14
14
  Max: 25
15
15
 
16
+ Metrics/ParameterLists:
17
+ CountKeywordArgs: false
18
+
16
19
 
17
20
  AllCops:
18
21
  Exclude:
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 0.2.0
2
+ Features:
3
+ - Allow unknown options to be ignored and not raise Error(#1)
4
+ - Added list options. For options that can be multiple times in the same command(#2)
5
+ - Added default value for options.(#3)
6
+ ## 0.1.0
7
+ Inital release.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Build Status](https://travis-ci.org/timcolonel/clin.svg?branch=master)](https://travis-ci.org/timcolonel/clin)
3
3
  [![Coverage Status](https://coveralls.io/repos/timcolonel/clin/badge.svg?branch=master)](https://coveralls.io/r/timcolonel/clin?branch=master)
4
4
  [![Code Climate](https://codeclimate.com/github/timcolonel/clin/badges/gpa.svg)](https://codeclimate.com/github/timcolonel/clin)
5
-
5
+ [![Inline docs](http://inch-ci.org/github/timcolonel/clin.svg?branch=master)](http://inch-ci.org/github/timcolonel/clin)
6
6
  Clin is Command Line Interface library that provide an clean api for complex command configuration.
7
7
  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
8
8
  ## Installation
@@ -5,8 +5,6 @@ require 'clin'
5
5
  class DisplayCommand < Clin::Command
6
6
  arguments 'display <message>'
7
7
 
8
- general_option Clin::HelpOptions
9
-
10
8
  self.description = 'Display the given message'
11
9
 
12
10
  def run
@@ -18,8 +16,6 @@ end
18
16
  class PrintCommand < Clin::Command
19
17
  arguments 'print <message>'
20
18
 
21
- general_option Clin::HelpOptions
22
-
23
19
  self.description = 'Print the given message'
24
20
 
25
21
  def run
@@ -27,50 +23,26 @@ class PrintCommand < Clin::Command
27
23
  end
28
24
  end
29
25
 
30
- Clin::CommandDispatcher.parse('display "My Message"').run
31
- puts
32
- puts '=' * 60
33
- puts
34
- Clin::CommandDispatcher.parse('print "My Message"').run
35
- puts
36
- puts '=' * 60
37
- puts
38
- begin
39
- Clin::CommandDispatcher.parse('display -h').run
40
- rescue Clin::CommandLineError => e
41
- puts e
42
- end
43
- puts
44
- puts '=' * 60
45
- puts
46
- begin
47
- Clin::CommandDispatcher.parse('-h')
48
- rescue Clin::CommandLineError => e
49
- puts e
26
+ if __FILE__ == $PROGRAM_NAME
27
+ Clin::CommandDispatcher.parse('display "My Message"').run
28
+ puts
29
+ puts '=' * 60
30
+ puts
31
+ Clin::CommandDispatcher.parse('print "My Message"').run
32
+ puts
33
+ puts '=' * 60
34
+ puts
35
+ begin
36
+ Clin::CommandDispatcher.parse('display -h').run
37
+ rescue Clin::CommandLineError => e
38
+ puts e
39
+ end
40
+ puts
41
+ puts '=' * 60
42
+ puts
43
+ begin
44
+ Clin::CommandDispatcher.parse('-h')
45
+ rescue Clin::CommandLineError => e
46
+ puts e
47
+ end
50
48
  end
51
-
52
- # Output:
53
- #
54
- # $ ruby dispatcher.rb
55
- # Display: 'My Message'
56
- #
57
- # ============================================================
58
- #
59
- # Print: 'My Message'
60
- #
61
- # ============================================================
62
- #
63
- # Usage: command display <message> [Options]
64
- #
65
- # Options:
66
- # -h, --help Show the help.
67
- #
68
- # Description:
69
- # Display the given message
70
- #
71
- #
72
- # ============================================================
73
- #
74
- # Usage:
75
- # command display <message> [Options]
76
- # command print <message> [Options]
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
+ require 'clin'
3
+ require 'clin'
4
+
5
+ # Simple command Example
6
+ class ListCommand < Clin::Command
7
+ list_option :echo, 'Echo some text'
8
+ list_flag_option :line, 'Print a line in between'
9
+ general_option Clin::HelpOptions
10
+
11
+ def run
12
+ @params[:echo].each do |msg|
13
+ puts msg
14
+ params[:line].times do
15
+ puts
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ if __FILE__ == $PROGRAM_NAME
22
+ ListCommand.parse('--echo "Message 1" --echo "Message 2"').run
23
+ puts
24
+ puts '=' * 60
25
+ puts
26
+ ListCommand.parse('--echo "Message 3" --echo "Message 4" -ll').run
27
+ end
@@ -5,7 +5,9 @@ require 'clin'
5
5
  class DispatchCommand < Clin::Command
6
6
  arguments 'you <args>...'
7
7
  dispatch :args, prefix: 'you'
8
- general_option Clin::HelpOptions
8
+ skip_options true
9
+
10
+ flag_option :verbose, 'Verbose the output'
9
11
 
10
12
  self.description = 'YOU print the given message'
11
13
 
@@ -17,8 +19,8 @@ end
17
19
  # Simple command Example
18
20
  class DisplayCommand < DispatchCommand
19
21
  arguments 'you display <message>'
20
-
21
- general_option Clin::HelpOptions
22
+ option :echo, 'Display more text'
23
+ option :times, 'Display the text multiple times', type: Integer
22
24
 
23
25
  self.description = 'Display the given message'
24
26
 
@@ -31,8 +33,6 @@ end
31
33
  class PrintCommand < DispatchCommand
32
34
  arguments 'you print <message>'
33
35
 
34
- general_option Clin::HelpOptions
35
-
36
36
  self.description = 'Print the given message'
37
37
 
38
38
  def run
@@ -40,27 +40,28 @@ class PrintCommand < DispatchCommand
40
40
  end
41
41
  end
42
42
 
43
-
44
- Clin::CommandDispatcher.parse('you display "My Message"').run
45
- puts
46
- puts '=' * 60
47
- puts
48
- Clin::CommandDispatcher.parse('you print "My Message"').run
49
- puts
50
- puts '=' * 60
51
- puts
52
- begin
53
- Clin::CommandDispatcher.parse('you -h').run
54
- rescue Clin::CommandLineError => e
55
- puts e
56
- end
57
- puts
58
- puts '=' * 60
59
- puts
60
- begin
61
- Clin::CommandDispatcher.parse('-h')
62
- rescue Clin::CommandLineError => e
63
- puts e
43
+ if __FILE__ == $PROGRAM_NAME
44
+ Clin::CommandDispatcher.parse('you display "My Message"').run
45
+ puts
46
+ puts '=' * 60
47
+ puts
48
+ Clin::CommandDispatcher.parse('you print "My Message"').run
49
+ puts
50
+ puts '=' * 60
51
+ puts
52
+ begin
53
+ Clin::CommandDispatcher.parse('you -h').run
54
+ rescue Clin::CommandLineError => e
55
+ puts e
56
+ end
57
+ puts
58
+ puts '=' * 60
59
+ puts
60
+ begin
61
+ Clin::CommandDispatcher.parse('-h')
62
+ rescue Clin::CommandLineError => e
63
+ puts e
64
+ end
64
65
  end
65
66
 
66
67
  # Output:
@@ -88,4 +89,4 @@ end
88
89
  # Usage:
89
90
  # command you <args>... [Options]
90
91
  # command you display <message> [Options]
91
- # command you print <message> [Options]
92
+ # command you print <message> [Options]
data/examples/simple.rb CHANGED
@@ -15,7 +15,7 @@ class SimpleCommand < Clin::Command
15
15
  end
16
16
  end
17
17
 
18
- if __FILE__== $0
18
+ if __FILE__ == $PROGRAM_NAME
19
19
  SimpleCommand.parse('display "My Message" --echo SOME').run
20
20
  puts
21
21
  puts '=' * 60
data/examples/test.rb CHANGED
@@ -1,2 +1,9 @@
1
1
  $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
- require 'clin'
2
+ require 'clin'
3
+
4
+ a = [1, 2, 3]
5
+
6
+ b, c = a
7
+
8
+ puts b
9
+ puts c
data/lib/clin/argument.rb CHANGED
@@ -18,14 +18,16 @@ class Clin::Argument
18
18
  @name = check_variable(argument)
19
19
  end
20
20
 
21
+ # Check if the argument is optional(i.e [arg])
21
22
  def check_optional(argument)
22
- if check_between(argument, '[', ']')
23
+ if beck_between(argument, '[', ']')
23
24
  @optional = true
24
25
  return argument[1...-1]
25
26
  end
26
27
  argument
27
28
  end
28
29
 
30
+ # Check if the argument is multiple(i.e arg...)
29
31
  def check_multiple(argument)
30
32
  if argument.end_with? '...'
31
33
  @multiple = true
@@ -34,8 +36,9 @@ class Clin::Argument
34
36
  argument
35
37
  end
36
38
 
39
+ # Check if the argument is variable(i.e <arg>)
37
40
  def check_variable(argument)
38
- if check_between(argument, '<', '>')
41
+ if beck_between(argument, '<', '>')
39
42
  @variable = true
40
43
  return argument[1...-1]
41
44
  end
@@ -46,21 +49,21 @@ class Clin::Argument
46
49
  def parse(argv)
47
50
  return handle_empty if argv.empty?
48
51
  if @multiple
49
- ensure_name(argv) unless @variable
52
+ ensure_fixed(argv) unless @variable
50
53
  [argv, []]
51
54
  else
52
- ensure_name(argv[0]) unless @variable
55
+ ensure_fixed(argv[0]) unless @variable
53
56
  [argv[0], argv[1..-1]]
54
57
  end
55
58
  end
56
59
 
57
- private
60
+ protected
58
61
 
59
- def ensure_name(args)
62
+ # Ensure the argument are equal to the fix value
63
+ def ensure_fixed(args)
60
64
  [*args].each do |arg|
61
- if arg != @name
62
- fail Clin::FixedArgumentError, @name, arg
63
- end
65
+ next if arg == @name
66
+ fail Clin::FixedArgumentError, @name, arg
64
67
  end
65
68
  end
66
69
 
@@ -78,10 +81,24 @@ class Clin::Argument
78
81
  end
79
82
  end
80
83
 
81
- def check_between(argument, start_char, end_char)
84
+ # Check +argument+ start with +start_char+ and end with +end_char+
85
+ # @param argument [String]
86
+ # @param start_char [Char]
87
+ # @param end_char [Char]
88
+ # @return [Boolean]
89
+ # @raise [Clin::Error] if it start but not end with.
90
+ # ```
91
+ # beck_between('[arg]', '['. ']') # => true
92
+ # beck_between('<arg>', '<'. '>') # => true
93
+ # beck_between('[<arg>]', '['. ']') # => true
94
+ # beck_between('[<arg>]', '<'. '>') # => false
95
+ # beck_between('[<arg>', '<'. '>') # => raise Clin::Error
96
+ # ```
97
+ def beck_between(argument, start_char, end_char)
82
98
  if argument[0] == start_char
83
99
  if argument[-1] != end_char
84
- fail Clin::Error, "Argument format error! Cannot start with #{start_char} and not end with #{end_char}"
100
+ fail Clin::Error, "Argument format error! Cannot start
101
+ with #{start_char} and not end with #{end_char}"
85
102
  end
86
103
  return true
87
104
  end
data/lib/clin/command.rb CHANGED
@@ -6,20 +6,19 @@ require 'clin/common/help_options'
6
6
 
7
7
  # Clin Command
8
8
  class Clin::Command < Clin::CommandOptionsMixin
9
-
10
9
  class_attribute :args
11
10
  class_attribute :description
12
11
 
13
-
14
12
  # Redispatch will be reset to nil when inheriting a dispatcher command
15
13
  class_attribute :_redispatch_args
16
14
  class_attribute :_abstract
17
15
  class_attribute :_exe_name
16
+ class_attribute :_skip_options
18
17
 
19
18
  self.args = []
20
19
  self.description = ''
21
20
  self._abstract = false
22
-
21
+ self._skip_options = false
23
22
 
24
23
  # Trigger when a class inherit this class
25
24
  # Rest class_attributes that should not be shared with subclass
@@ -27,6 +26,7 @@ class Clin::Command < Clin::CommandOptionsMixin
27
26
  def self.inherited(subclass)
28
27
  subclass._redispatch_args = nil
29
28
  subclass._abstract = false
29
+ subclass._skip_options = false
30
30
  super
31
31
  end
32
32
 
@@ -46,11 +46,23 @@ class Clin::Command < Clin::CommandOptionsMixin
46
46
  # end
47
47
  # Git.usage # => git <command> <args>...
48
48
  # ```
49
- def self.exe_name(value=nil)
49
+ def self.exe_name(value = nil)
50
50
  self._exe_name = value unless value.nil?
51
51
  self._exe_name ||= Clin.exe_name
52
52
  end
53
53
 
54
+ def self.skip_options(value)
55
+ self._skip_options = value
56
+ end
57
+
58
+ def self.skip_options?
59
+ _skip_options
60
+ end
61
+
62
+ def self.redispatch?
63
+ !_redispatch_args.nil?
64
+ end
65
+
54
66
  def self.arguments(args)
55
67
  self.args = []
56
68
  [*args].map(&:split).flatten.each do |arg|
@@ -70,37 +82,8 @@ class Clin::Command < Clin::CommandOptionsMixin
70
82
  # Parse the command and initialize the command object with the parsed options
71
83
  # @param argv [Array|String] command line to parse.
72
84
  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
85
+ parser = Clin::CommandParser.new(self, argv, fallback_help: fallback_help)
86
+ parser.parse
104
87
  end
105
88
 
106
89
  # Build the Option Parser object
@@ -121,22 +104,6 @@ class Clin::Command < Clin::CommandOptionsMixin
121
104
  end
122
105
  end
123
106
 
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
107
  # Redispatch the command to a sub command with the given arguments
141
108
  # @param args [Array<String>|String] New argument to parse
142
109
  # @param prefix [String] Prefix to add to the beginning of the command
@@ -149,46 +116,32 @@ class Clin::Command < Clin::CommandOptionsMixin
149
116
  self._redispatch_args = [[*args], prefix, commands]
150
117
  end
151
118
 
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
119
  def self.dispatch_doc(opts)
168
- return if self._redispatch_args.nil?
120
+ return if _redispatch_args.nil?
169
121
  opts.separator 'Examples: '
170
- commands = (self._redispatch_args[2] || default_commands)
122
+ commands = (_redispatch_args[2] || default_commands)
171
123
  commands.each do |cmd_cls|
172
124
  opts.separator "\t#{cmd_cls.usage}"
173
125
  end
174
126
  end
175
127
 
176
128
  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
129
+ # self.constants.map { |c| self.const_get(c) }
130
+ # .select { |c| c.is_a?(Class) && (c < Clin::Command) }
131
+ subcommands
179
132
  end
180
133
 
181
134
  # List the subcommands
182
135
  # The subcommands are all the Classes inheriting this one that are not set to abstract
183
136
  def self.subcommands
184
- self.subclasses.reject(&:_abstract)
137
+ subclasses.reject(&:_abstract)
185
138
  end
186
139
 
187
140
  general_option 'Clin::HelpOptions'
188
141
 
189
142
  attr_accessor :params
190
143
 
191
- def initialize(params)
144
+ def initialize(params = {})
192
145
  @params = params
193
146
  self.class.execute_general_options(params)
194
147
  end
@@ -3,6 +3,7 @@ require 'clin/command'
3
3
 
4
4
  # Class charge dispatching the CL to the right command
5
5
  class Clin::CommandDispatcher
6
+ # Contains the list of commands the dispatch will test.
6
7
  attr_accessor :commands
7
8
 
8
9
  # Create a new command dispatcher.
@@ -32,7 +33,7 @@ class Clin::CommandDispatcher
32
33
 
33
34
  # Helper method to parse against all the commands
34
35
  # @see #parse
35
- def self.parse(argv=ARGV)
36
+ def self.parse(argv = ARGV)
36
37
  Clin::CommandDispatcher.new.parse(argv)
37
38
  end
38
39
 
@@ -1,5 +1,6 @@
1
1
  require 'clin'
2
2
  require 'clin/option'
3
+ require 'clin/option_list'
3
4
 
4
5
  # Template class for reusable options and commands
5
6
  # It provide the method to add options to a command
@@ -9,7 +10,6 @@ class Clin::CommandOptionsMixin
9
10
  self.options = []
10
11
  self.general_options = {}
11
12
 
12
-
13
13
  # Add an option
14
14
  # @param args list of arguments.
15
15
  # * First argument must be the name if no block is given.
@@ -52,6 +52,21 @@ class Clin::CommandOptionsMixin
52
52
  add_option Clin::Option.new(name, description, **config.merge(argument: false), &block)
53
53
  end
54
54
 
55
+ # Add a list option.
56
+ # @see Clin::OptionList#initialize
57
+ def self.list_option(name, description, **config)
58
+ add_option Clin::OptionList.new(name, description, **config)
59
+ end
60
+
61
+ # Add a list options that don't take arguments
62
+ # Same as .list_option but set +argument+ to false
63
+ # @see Clin::OptionList#initialize
64
+ def self.list_flag_option(name, description, **config)
65
+ add_option Clin::OptionList.new(name, description, **config.merge(argument: false))
66
+ end
67
+
68
+ # Add a new option.
69
+ # @param option [Clin::Option] option to add.
55
70
  def self.add_option(option)
56
71
  # Need to use += instead of << otherwise the parent class will also be changed
57
72
  self.options += [option]
@@ -62,14 +77,14 @@ class Clin::CommandOptionsMixin
62
77
  # @param config [Hash] General option config. Check the general option config.
63
78
  def self.general_option(option_cls, config = {})
64
79
  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))
80
+ self.general_options = general_options.merge(option_cls => option_cls.new(config))
66
81
  end
67
82
 
68
83
  # Remove a general option
69
84
  # Might be useful if a parent added the option but is not needed in this child.
70
85
  def self.remove_general_option(option_cls)
71
86
  option_cls = option_cls.constantize if option_cls.is_a? String
72
- self.general_options = self.general_options.except(option_cls)
87
+ self.general_options = general_options.except(option_cls)
73
88
  end
74
89
 
75
90
  # To be called inside OptionParser block
@@ -85,4 +100,19 @@ class Clin::CommandOptionsMixin
85
100
  option.class.register_options(opts, out)
86
101
  end
87
102
  end
103
+
104
+ # Call #execute on each of the general options.
105
+ # This is called during the command initialization
106
+ # e.g. A verbose general option execute would be:
107
+ # ```
108
+ # def execute(params)
109
+ # MyApp.verbose = true if params[:verbose]
110
+ # end
111
+ # ```
112
+ def self.execute_general_options(options)
113
+ general_options.each do |_cls, gopts|
114
+ gopts.execute(options)
115
+ end
116
+ end
117
+
88
118
  end