clin 0.1.0 → 0.2.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 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