slop 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGES.md +10 -1
  2. data/lib/slop.rb +130 -43
  3. data/slop.gemspec +1 -1
  4. data/test/slop_test.rb +35 -0
  5. metadata +2 -2
data/CHANGES.md CHANGED
@@ -1,5 +1,14 @@
1
+ 2.1.0 (2011-08-03)
2
+ ------------------
3
+
4
+ * Added `Slop#missing` for returning a list of missing options parsed
5
+ * Allow `Slop#present?` to accept multiple arguments
6
+ * Added `:all_accept_arguments` to Slop configuration options, this saves
7
+ having to specify that every option takes an argument
8
+ * Added `Slop#to_struct` for building new classes from options
9
+
1
10
  2.0.0 (2011-07-07)
2
- -----------
11
+ ------------------
3
12
 
4
13
  * Deprecations:
5
14
  * Removed `Slop::Options#to_hash` continue using `Slop#to_hash` directly.
data/lib/slop.rb CHANGED
@@ -23,23 +23,56 @@ class Slop
23
23
  class Option < Struct.new(:short_flag, :long_flag, :description,
24
24
  :tail, :match, :help, :required, :forced, :count)
25
25
 
26
- # @param [Slop] slop
27
- # @param [String, #to_s] short
28
- # @param [String, #to_s] long
29
- # @param [String] description
30
- # @param [Boolean] argument
31
- # @param [Hash] options
26
+ # @param [Slop] slop The Slop object this Option belongs to
27
+ #
28
+ # @param [String, #to_s] short The short flag representing this Option
29
+ # without prefix (ie: `a`)
30
+ #
31
+ # @param [String, #to_s] long The long flag representing this Option
32
+ # without the prefix (ie: `foo`)
33
+ #
34
+ # @param [String] description This options description
35
+ #
36
+ # @param [Boolean] argument True if this option takes an argument
37
+ #
32
38
  # @option options [Boolean] :optional
39
+ # * When true, this option takes an optional argument, ie an argument
40
+ # does not **have** to be supplied.
41
+ #
33
42
  # @option options [Boolean] :argument
43
+ # * True if this option takes an argument.
44
+ #
34
45
  # @option options [Object] :default
46
+ # * The default value for this option when no argument is given
47
+ #
35
48
  # @option options [Proc, #call] :callback
49
+ # * The callback object, used instead of passing a block to this option
50
+ #
36
51
  # @option options [String, #to_s] :delimiter (',')
52
+ # * A delimiter string when processing this option as a list
53
+ #
37
54
  # @option options [Integer] :limit (0)
55
+ # * A limit, used when processing this option as a list
56
+ #
38
57
  # @option options [Boolean] :tail (false)
58
+ # * When true, this option will be grouped at the bottom of the help
59
+ # text instead of in order of processing
60
+ #
39
61
  # @option options [Regexp] :match
62
+ # * A regular expression this option should match
63
+ #
40
64
  # @option options [String, #to_s] :unless
65
+ # * Used by `omit_exec` for omitting execution of this options callback
66
+ # if another option exists
67
+ #
41
68
  # @option options [Boolean, String] :help (true)
69
+ # * If this option is a string, it'll be appended to the long flag
70
+ # help text (before the description). When false, no help information
71
+ # will be displayed for this option
72
+ #
42
73
  # @option options [Boolean] :required (false)
74
+ # * When true, this option is considered mandatory. That is, when not
75
+ # supplied, Slop will raise a `MissingOptionError`
43
76
  def initialize(slop, short, long, description, argument, options, &blk)
44
77
  @slop = slop
45
78
 
@@ -66,7 +99,10 @@ class Slop
66
99
  @callback = blk if block_given?
67
100
  @callback ||= @options[:callback]
68
101
 
69
- build_longest_flag
102
+ if long_flag && long_flag.size > @slop.longest_flag
103
+ @slop.longest_flag = long_flag.size
104
+ @slop.longest_flag += help.size if help.respond_to?(:to_str)
105
+ end
70
106
  end
71
107
 
72
108
  # @return [Boolean] true if this option expects an argument
@@ -194,13 +230,6 @@ class Slop
194
230
  end
195
231
  end
196
232
 
197
- def build_longest_flag
198
- if long_flag && long_flag.size > @slop.longest_flag
199
- @slop.longest_flag = long_flag.size
200
- @slop.longest_flag += help.size if help.respond_to? :to_str
201
- end
202
- end
203
-
204
233
  end
205
234
 
206
235
  # Used to hold a list of Option objects. This class inherits from Array
@@ -234,7 +263,7 @@ class Slop
234
263
  end
235
264
 
236
265
  # @return [String] The current version string
237
- VERSION = '2.0.0'
266
+ VERSION = '2.1.0'
238
267
 
239
268
  # Parses the items from a CLI format into a friendly object
240
269
  #
@@ -329,9 +358,13 @@ class Slop
329
358
  # @option opts [Boolean] :completion (true)
330
359
  # * When true, commands will be auto completed. Ie `foobar` will be
331
360
  # executed simply when `foo` `fo` or `foob` are used
361
+ #
362
+ # @option options [Boolean] :all_accept_arguments (false)
363
+ # * When true, every option added will take an argument, this saves
364
+ # having to enable it for every option
332
365
  def initialize(*opts, &block)
333
366
  sloptions = opts.last.is_a?(Hash) ? opts.pop : {}
334
- sloptions[:banner] = opts.shift if opts[0].respond_to? :to_str
367
+ sloptions[:banner] = opts.shift if opts[0].respond_to?(:to_str)
335
368
  opts.each { |o| sloptions[o] = true }
336
369
 
337
370
  @options = Options.new
@@ -433,7 +466,7 @@ class Slop
433
466
  option = @options[key]
434
467
  option ? option.argument_value : @commands[key]
435
468
  end
436
- alias :get :[]
469
+ alias get []
437
470
 
438
471
  # Specify an option with a short or long version, description and type
439
472
  #
@@ -459,15 +492,18 @@ class Slop
459
492
  def option(*args, &block)
460
493
  options = args.last.is_a?(Hash) ? args.pop : {}
461
494
 
462
- short, long, desc, arg, extras = clean_options args
495
+ short, long, desc, arg, extras = clean_options(args)
496
+
463
497
  options.merge!(extras)
464
- option = Option.new self, short, long, desc, arg, options, &block
498
+ options[:argument] = true if @sloptions[:all_accept_arguments]
499
+
500
+ option = Option.new(self, short, long, desc, arg, options, &block)
465
501
  @options << option
466
502
 
467
503
  option
468
504
  end
469
- alias :opt :option
470
- alias :on :option
505
+ alias opt option
506
+ alias on option
471
507
 
472
508
  # Namespace options depending on what command is executed
473
509
  #
@@ -515,7 +551,7 @@ class Slop
515
551
  def on_empty(obj=nil, &block)
516
552
  @on_empty ||= (obj || block)
517
553
  end
518
- alias :on_empty= :on_empty
554
+ alias on_empty= on_empty
519
555
 
520
556
  # Trigger an event when the arguments contain no options
521
557
  #
@@ -529,7 +565,7 @@ class Slop
529
565
  def on_noopts(obj=nil, &block)
530
566
  @on_noopts ||= (obj || block)
531
567
  end
532
- alias :on_optionless :on_noopts
568
+ alias on_optionless on_noopts
533
569
 
534
570
  # Add an execution block (for commands)
535
571
  #
@@ -546,7 +582,7 @@ class Slop
546
582
  # @param [Array] args The list of arguments to send to this command
547
583
  # is invoked
548
584
  # @since 1.8.0
549
- # @yields [Slop] an instance of Slop for this command
585
+ # @yield [Slop] an instance of Slop for this command
550
586
  def execute(args=[], &block)
551
587
  if block_given?
552
588
  @execution_block = block
@@ -571,7 +607,52 @@ class Slop
571
607
  hsh
572
608
  end
573
609
  end
574
- alias :to_h :to_hash
610
+ alias to_h to_hash
611
+
612
+ # Return parsed items as a new Class
613
+ #
614
+ # @example
615
+ # opts = Slop.new do
616
+ # on :n, :name, 'Persons name', true
617
+ # on :a, :age, 'Persons age', true, :as => :int
618
+ # on :s, :sex, 'Persons sex m/f', true, :match => /^[mf]$/
619
+ # on :A, :admin, 'Enable admin mode'
620
+ # end
621
+ #
622
+ # opts.parse %w[ --name Lee --age 22 -s m --admin ]
623
+ #
624
+ # person = opts.to_struct("Person")
625
+ # person.class #=> Struct::Person
626
+ # person.name #=> 'Lee'
627
+ # person.age #=> 22
628
+ # person.sex #=> m
629
+ # person.admin #=> true
630
+ #
631
+ # @param [String] name The name of this class
632
+ # @return [Class] The new class, or nil if there are no options
633
+ # @since 2.0.0
634
+ def to_struct(name=nil)
635
+ hash = to_hash
636
+ Struct.new(name, *hash.keys).new(*hash.values) unless hash.empty?
637
+ end
638
+
639
+ # Fetch a list of options which were missing from the parsed list
640
+ #
641
+ # @example
642
+ # opts = Slop.new do
643
+ # on :n, :name, 'Your name', true
644
+ # on :p, :password, 'Your password', true
645
+ # on :A, 'Use auth?'
646
+ # end
647
+ #
648
+ # opts.parse %w[ --name Lee ]
649
+ # opts.missing #=> ['password', 'a']
650
+ #
651
+ # @return [Array] A list of options missing from the parsed string
652
+ # @since 2.1.0
653
+ def missing
654
+ @options.select { |opt| not present?(opt.key) }.map { |opt| opt.key }
655
+ end
575
656
 
576
657
  # Allows you to check whether an option was specified in the parsed list
577
658
  #
@@ -599,11 +680,11 @@ class Slop
599
680
  # Does the same as Slop#option? but a convenience method for unacceptable
600
681
  # method names
601
682
  #
602
- # @param [Object] The object name to check
683
+ # @param [Object] The object name(s) to check
603
684
  # @since 1.5.0
604
- # @return [Boolean] true if this option is present, false otherwise
605
- def present?(option_name)
606
- @options[option_name] && @options[option_name].count > 0
685
+ # @return [Boolean] true if these options are present, false otherwise
686
+ def present?(*option_names)
687
+ option_names.all? { |opt| @options[opt] && @options[opt].count > 0 }
607
688
  end
608
689
 
609
690
  # Returns the banner followed by available options listed on the next line
@@ -634,7 +715,7 @@ class Slop
634
715
 
635
716
  parts.join("\n\n")
636
717
  end
637
- alias :help :to_s
718
+ alias help to_s
638
719
 
639
720
  # @return [String] This Slop object will options and configuration
640
721
  # settings revealed
@@ -669,10 +750,10 @@ class Slop
669
750
  # * Remove non-parsed items if `delete` is true
670
751
  # * Yield any non-options to the block (if one is given)
671
752
  def parse_items(items, delete=false, &block)
672
- if items.empty? && @on_empty.respond_to?(:call)
753
+ if items.empty? and @on_empty.respond_to?(:call)
673
754
  @on_empty.call self
674
755
  return items
675
- elsif !items.any? {|i| i.to_s[/\A--?/] } && @on_noopts.respond_to?(:call)
756
+ elsif not items.any? {|i| i.to_s[/\A--?/] } and @on_noopts.respond_to?(:call)
676
757
  @on_noopts.call self
677
758
  return items
678
759
  elsif execute_command(items, delete)
@@ -695,7 +776,7 @@ class Slop
695
776
  autocreate(flag, index, items) if @autocreate
696
777
  option, argument = extract_option(item, flag)
697
778
 
698
- if @multiple_switches && item[/\A-[^-]/] && !option
779
+ if @multiple_switches and item[/\A-[^-]/] and not option
699
780
  trash << index
700
781
  next
701
782
  end
@@ -706,16 +787,16 @@ class Slop
706
787
  next if option.forced
707
788
  option.argument_value = true
708
789
 
709
- if option.expects_argument? || option.accepts_optional_argument?
790
+ if option.expects_argument? or option.accepts_optional_argument?
710
791
  argument ||= items.at(index + 1)
711
792
  trash << index + 1
712
793
 
713
- if !option.accepts_optional_argument? && argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/
794
+ if not option.accepts_optional_argument? and argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/
714
795
  raise MissingArgumentError, "'#{option.key}' expects an argument, none given"
715
796
  end
716
797
 
717
798
  if argument
718
- if option.match && !argument.match(option.match)
799
+ if option.match and not argument.match(option.match)
719
800
  raise InvalidArgumentError, "'#{argument}' does not match #{option.match.inspect}"
720
801
  end
721
802
 
@@ -729,8 +810,8 @@ class Slop
729
810
  option.call unless option.omit_exec?(items)
730
811
  end
731
812
  else
732
- @invalid_options << flag if item[/\A--?/] && @strict
733
- block.call(item) if block_given? && !trash.include?(index)
813
+ @invalid_options << flag if item[/\A--?/] and @strict
814
+ block.call(item) if block_given? and not trash.include?(index)
734
815
  end
735
816
  end
736
817
 
@@ -749,7 +830,7 @@ class Slop
749
830
  end
750
831
 
751
832
  def raise_if_invalid_options!
752
- return if !@strict || @invalid_options.empty?
833
+ return if not @strict or @invalid_options.empty?
753
834
  message = "Unknown option#{'s' if @invalid_options.size > 1}"
754
835
  message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ')
755
836
  raise InvalidOptionError, message
@@ -770,10 +851,13 @@ class Slop
770
851
  # flag was not found
771
852
  def enable_multiple_switches(item)
772
853
  item[1..-1].each_char do |switch|
773
- if option = @options[switch]
854
+ option = @options[switch]
855
+
856
+ if option
774
857
  if option.expects_argument?
775
858
  raise MissingArgumentError, "'-#{switch}' expects an argument, used in multiple_switch context"
776
859
  end
860
+
777
861
  option.argument_value = true
778
862
  option.count += 1
779
863
  else
@@ -812,6 +896,7 @@ class Slop
812
896
  option = @options[flag]
813
897
  option ||= @options[flag.downcase] if @ignore_case
814
898
  end
899
+
815
900
  unless option
816
901
  case item
817
902
  when /\A-[^-]/
@@ -828,6 +913,7 @@ class Slop
828
913
  option.force_argument_value(false) if option
829
914
  end
830
915
  end
916
+
831
917
  [option, argument]
832
918
  end
833
919
 
@@ -881,7 +967,7 @@ class Slop
881
967
  def clean_options(args)
882
968
  options = []
883
969
  extras = {}
884
- extras[:as] =args.find {|c| c.is_a? Class }
970
+ extras[:as] = args.find { |c| c.is_a? Class }
885
971
  args.delete(extras[:as])
886
972
  extras.delete(:as) if extras[:as].nil?
887
973
 
@@ -895,12 +981,13 @@ class Slop
895
981
 
896
982
  long = args.first
897
983
  boolean = [true, false].include? long
898
- if !boolean && long.to_s =~ /\A(?:--?)?[a-z_-]+\s[A-Z\s\[\]]+\z/
984
+
985
+ if not boolean and long.to_s =~ /\A(?:--?)?[a-z_-]+\s[A-Z\s\[\]]+\z/
899
986
  arg, help = args.shift.split(/ /, 2)
900
987
  options.push arg.sub(/\A--?/, '')
901
988
  extras[:optional] = help[0, 1] == '[' && help[-1, 1] == ']'
902
989
  extras[:help] = help
903
- elsif !boolean && long.to_s =~ /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\z/
990
+ elsif not boolean and long.to_s =~ /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\z/
904
991
  options.push args.shift.to_s.sub(/\A--?/, '')
905
992
  else
906
993
  options.push nil
data/slop.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'slop'
3
- s.version = '2.0.0'
3
+ s.version = '2.1.0'
4
4
  s.summary = 'Option gathering made easy'
5
5
  s.description = 'A simple DSL for gathering options and parsing the command line'
6
6
  s.author = 'Lee Jarvis'
data/test/slop_test.rb CHANGED
@@ -112,6 +112,18 @@ class SlopTest < TestCase
112
112
  assert_equal 'Print this help message', slop.options[:help].description
113
113
  end
114
114
 
115
+ test ':all_accept_arguments' do
116
+ opts = Slop.new(:all_accept_arguments) do
117
+ on :foo
118
+ on :bar, :optional => true
119
+ end
120
+ opts.parse %w[ --foo hello --bar ]
121
+
122
+ assert_equal 'hello', opts[:foo]
123
+ assert_nil opts[:bar]
124
+ assert_raises(Slop::MissingArgumentError) { opts.parse %w[ --foo --bar ] }
125
+ end
126
+
115
127
  test 'yielding non-options when a block is passed to "parse"' do
116
128
  opts = Slop.new do
117
129
  on :name, true
@@ -300,6 +312,7 @@ class SlopTest < TestCase
300
312
 
301
313
  assert opts.present?('foo-bar')
302
314
  refute opts.present?('bar-baz')
315
+ refute opts.present?('foo-bar', 'bar-baz')
303
316
  assert opts.present?(:h)
304
317
  end
305
318
 
@@ -517,4 +530,26 @@ class SlopTest < TestCase
517
530
  " labore et dolore magna aliqua.",
518
531
  slop.send(:wrap_and_indent, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 36, 4))
519
532
  end
533
+
534
+ test 'to_struct' do
535
+ assert_nil Slop.new.to_struct
536
+
537
+ slop = Slop.new { on :a, true }
538
+ slop.parse %w[ -a foo -b ]
539
+ struct = slop.to_struct
540
+
541
+ assert_equal 'foo', struct.a
542
+ assert_kind_of Struct, struct
543
+ assert_raises(NoMethodError) { struct.b }
544
+
545
+ pstruct = slop.to_struct('Foo')
546
+ assert_kind_of Struct::Foo, pstruct
547
+ end
548
+
549
+ test 'returning missing options' do
550
+ slop = Slop.new { on :a; on :b, :bar; on :c; }
551
+ slop.parse %w[ -a ]
552
+
553
+ assert_equal %w[ bar c ], slop.missing
554
+ end
520
555
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slop
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-07 00:00:00.000000000 +01:00
12
+ date: 2011-08-03 00:00:00.000000000 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
  description: A simple DSL for gathering options and parsing the command line