slop 2.0.0 → 2.1.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 (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