clive 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -21,5 +21,5 @@ pkg
21
21
  ## PROJECT::SPECIFIC
22
22
  .yardoc
23
23
  doc
24
- ideas.rb
25
24
  research
25
+ cov
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{clive}
8
- s.version = "0.3.1"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Joshua Hawxwell"]
12
- s.date = %q{2010-08-21}
12
+ s.date = %q{2010-09-19}
13
13
  s.description = %q{Clive is a DSL for creating a command line interface. It is for people who, like me, love OptionParser's syntax and love GLI's commands.}
14
14
  s.email = %q{m@hawx.me}
15
15
  s.extra_rdoc_files = [
@@ -22,30 +22,75 @@ require 'clive/bool'
22
22
  # end
23
23
  # c.parse(ARGV)
24
24
  #
25
+ # @example Non-simple Example
26
+ #
27
+ # opts = {}
28
+ # c = Clive.new do
29
+ # bool(:v, :verbose, "Run verbosely") {|i| opts[:verbose] = i}
30
+ #
31
+ # command :add, "Add a new project" do
32
+ # opts[:add] = {}
33
+ #
34
+ # switch(:force, "Force overwrite") {opts[:add][:force] = true}
35
+ # flag :framework, "Add framework" do |i|
36
+ # opts[:add][:framework] ||= []
37
+ # opts[:add][:framework] << i
38
+ # end
39
+ #
40
+ # command :init, "Initialize the project after creating" do
41
+ # switch(:m, :minimum, "Use minimum settings") {opts[:add][:min] = true}
42
+ # flag(:w, :width) {|i| opts[:add][:width] = i.to_i}
43
+ # end
44
+ #
45
+ # end
46
+ #
47
+ # switch :version, "Show version" do
48
+ # puts "1.0.0"
49
+ # exit
50
+ # end
51
+ # end
52
+ # ARGV = c.parse(ARGV)
53
+ #
25
54
  class Clive
26
-
55
+
56
+ # This is the base command, the only way it differs from a normal command
57
+ # is that it has no name and it's block is executed immediately on creation.
58
+ #
59
+ # @return [Command] the base command
60
+ #
27
61
  attr_accessor :base
28
62
 
29
63
  def initialize(&block)
30
64
  @base = Command.new(true, &block)
31
65
  end
32
66
 
67
+ # Parse the base command
33
68
  def parse(argv)
34
69
  @base.run(argv)
35
70
  end
36
71
 
72
+ # @group Base Proxy Methods
73
+
74
+ # @see Command#switches
75
+ # @return [Array] switches in +base+
37
76
  def switches
38
77
  @base.switches
39
78
  end
40
79
 
80
+ # @see Command#commands
81
+ # @return [Array] commands in +base+
41
82
  def commands
42
83
  @base.commands
43
84
  end
44
85
 
86
+ # @see Command#flags
87
+ # @return [Array] flags in +base+
45
88
  def flags
46
89
  @base.flags
47
90
  end
48
91
 
92
+ # @see Command#bools
93
+ # @return [Array] bools in +base+
49
94
  def bools
50
95
  @base.bools
51
96
  end
@@ -1,13 +1,14 @@
1
1
  class Clive
2
2
 
3
- # A switch which can be triggered with either --no-verbose or --verbose
4
- # for example.
3
+ # A switch which can be triggered with either --no-[name] and --[name].
4
+ # The 'truthness' of this is then passed to the block.
5
5
  class Bool < Option
6
6
  attr_accessor :truth
7
7
 
8
8
  # Creates a new Bool switch instance. A boolean switch has a truth,
9
9
  # this determines what is passed to the block. They should be created
10
10
  # in pairs so one can be +--something+ the other +--no-something+.
11
+ # NOTE: this does not happen within this class!
11
12
  #
12
13
  # +short+ and/or +desc+ can be omitted when creating a Boolean, all
13
14
  # other arguments must be present.
@@ -19,6 +20,7 @@ class Clive
19
20
  # @param [String] desc description of use/purpose
20
21
  #
21
22
  # @yield [Boolean] A block to be run when the switch is triggered
23
+ # @raise [MissingLongName] raises when a long name is not given
22
24
  #
23
25
  def initialize(*args, truth, &block)
24
26
  @names = []
@@ -35,6 +37,7 @@ class Clive
35
37
  end
36
38
  end
37
39
 
40
+ # booleans require a long name to add --no- to
38
41
  unless @names.find_all {|i| i.length > 1}.length > 0
39
42
  raise MissingLongName, @names[0]
40
43
  end
@@ -48,6 +51,8 @@ class Clive
48
51
  @block.call(@truth)
49
52
  end
50
53
 
54
+ # @param [Integer] width the total ideal width of help
55
+ # @param [Integer] prepend the number of spaces to add before each line
51
56
  # @return [String] summary for help or nil if +@truth = false+
52
57
  def summary(width=30, prepend=5)
53
58
  return nil unless @truth
@@ -98,8 +98,15 @@ class Clive
98
98
  when :switch
99
99
  v.run
100
100
  when :flag
101
- raise MissingArgument.new(v.sort_name) unless i[2] || v.optional
102
- v.run(i[2])
101
+ args = i[2..-1]
102
+ opt_args = v.arg_num(true)
103
+ nec_args = v.arg_num(false)
104
+ # check for missing args
105
+ if args.size < nec_args
106
+ raise MissingArgument.new(v.sort_name)
107
+ end
108
+
109
+ v.run(args)
103
110
  when :argument
104
111
  r << v
105
112
  end
@@ -170,8 +177,8 @@ class Clive
170
177
  else
171
178
  if k == :word
172
179
  # add to last flag?
173
- if r.last && r.last[0] == :flag && r.last[2].nil?
174
- r.last[2] = v
180
+ if r.last && r.last[0] == :flag && r.last.size - 2 < r.last[1].args.size
181
+ r.last.push(v)
175
182
  else
176
183
  r << [:argument, v]
177
184
  end
@@ -261,6 +268,7 @@ class Clive
261
268
  if @options.length > 0
262
269
  summary << "\n Options:\n"
263
270
  @options.sort.each do |i|
271
+ next if i.names.include?("help")
264
272
  summary << i.summary(width, prepend) << "\n" if i.summary
265
273
  end
266
274
  end
@@ -1,6 +1,6 @@
1
1
  class Clive
2
2
 
3
- # general problem
3
+ # General problem
4
4
  class CliveError < StandardError
5
5
  attr_accessor :args
6
6
 
@@ -26,21 +26,27 @@ class Clive
26
26
 
27
27
  end
28
28
 
29
- # general problem with input
29
+ # General problem with input
30
30
  class ParseError < CliveError
31
31
  def reason; "parse error"; end
32
32
  end
33
33
 
34
- # a flag has a missing argument
34
+ # A flag has a missing argument
35
35
  class MissingArgument < ParseError
36
36
  def reason; "missing argument"; end
37
37
  end
38
38
 
39
- # a option that wasn't defined has been found
39
+ # A flag has a wrong argument
40
+ class InvalidArgument < ParseError
41
+ def reason; "invalid argument"; end
42
+ end
43
+
44
+ # An option that wasn't defined has been found
40
45
  class InvalidOption < ParseError
41
46
  def reason; "invalid option"; end
42
47
  end
43
48
 
49
+ # Long name is missing for bool
44
50
  class MissingLongName < CliveError
45
51
  def reason; "missing long name"; end
46
52
  end
@@ -2,7 +2,7 @@ class Clive
2
2
 
3
3
  class Array < ::Array
4
4
 
5
- # If passed a Symbol or String will get the item with that name.
5
+ # If passed a Symbol or String will get the option or command with that name.
6
6
  # Otherwise does what you expect of an Array (see ::Array#[])
7
7
  #
8
8
  # @param [Symbol, String, Integer, Range] val name or index of item to return
@@ -16,5 +16,38 @@ class Clive
16
16
  end
17
17
  end
18
18
 
19
+ # Attempts to fill +self+ with values from +input+, giving priority to
20
+ # true, then false. If insufficient input to fill all false will use nil.
21
+ #
22
+ # @param [Array] input array of values to fill +self+ with
23
+ # @return [Array] filled array
24
+ #
25
+ # @example
26
+ #
27
+ # [true, false, false, true].optimise_fill(["a", "b", "c"])
28
+ # #=> ["a", "b", nil, "c"]
29
+ #
30
+ #
31
+ def optimise_fill(input)
32
+ match = self
33
+ diff = input.size - match.reject{|i| i == false}.size
34
+
35
+ result = []
36
+ match.each_index do |i|
37
+ curr_item = match[i]
38
+ if curr_item == true
39
+ result << input.shift
40
+ else
41
+ if diff > 0
42
+ result << input.shift
43
+ diff -= 1
44
+ else
45
+ result << nil
46
+ end
47
+ end
48
+ end
49
+ result
50
+ end
51
+
19
52
  end
20
53
  end
@@ -1,44 +1,75 @@
1
1
  class Clive
2
2
 
3
- # A switch that takes an argument, with or without an equals
3
+ # A switch that takes one or more arguments.
4
4
  # eg. wget --tries=10
5
5
  # wget -t 10
6
6
  #
7
7
  class Flag < Option
8
- attr_accessor :arg_name, :optional
8
+ attr_accessor :args
9
9
 
10
- # Creates a new Flag instance.
10
+ # Creates a new Flag instance. A flag is a switch that can take one or more
11
+ # arguments.
11
12
  #
12
- # +short+ _or_ +long+ can be omitted but not both.
13
+ # +short+ *or* +long+ can be omitted but not both.
14
+ # +args+ can also be omitted (is "ARG" by default)
13
15
  #
14
- # @overload flag(short, long, desc, &block)
15
- # Creates a new flag
16
+ # @overload flag(short, long, args, desc, &block)
16
17
  # @param [Symbol] short single character for short flag, eg. +:t+ => +-t 10+
17
18
  # @param [Symbol] long longer switch to be used, eg. +:tries+ => +--tries=10+
19
+ # @param [String, Array] args
20
+ # either a string showing the arguments to be given, eg.
21
+ #
22
+ # "FROM" # single arg, or
23
+ # "FROM TO" # two args, or
24
+ # "[VIA]" # optional arg surrounded by square brackets
25
+ #
26
+ # or an array of acceptable inputs, eg.
27
+ #
28
+ # ["large", "medium", "small"] # will only accept these args
29
+ #
18
30
  # @param [String] desc the description for the flag
19
31
  #
20
32
  # @yield [String] A block to be run if switch is triggered
21
33
  #
22
34
  def initialize(*args, &block)
23
- @names = []
24
- @optional = false
25
- @arg_name = "ARG"
35
+ @names = []
36
+ @args = []
26
37
 
38
+ # Need to be able to make each arg_name optional or not
39
+ # and allow for type in future
27
40
  args.each do |i|
28
41
  if i.is_a? String
29
- if i =~ /^[\[\]A-Z0-9]+$/
30
- @arg_name = i
42
+ if i =~ /\A(([A-Z0-9\[\]]+)\s?)+\Z/
43
+ i.split(' ').each do |arg|
44
+ type = String
45
+ if arg[0] == "["
46
+ @args << {
47
+ :name => arg[1..arg.length-2],
48
+ :optional => true,
49
+ :type => type
50
+ }
51
+ else
52
+ @args << {
53
+ :name => arg,
54
+ :optional => false,
55
+ :type => type
56
+ }
57
+ end
58
+ end
31
59
  else
32
60
  @desc = i
33
61
  end
34
62
  else
35
- @names << i.to_s
63
+ if i.class == Symbol
64
+ @names << i.to_s
65
+ else
66
+ @args = i
67
+ end
36
68
  end
37
69
  end
38
70
 
39
- if @arg_name[0] == "["
40
- @arg_name = @arg_name[1..@arg_name.length-2]
41
- @optional = true
71
+ if @args == []
72
+ @args = [{:name => "ARG", :optional => false, :type => String}]
42
73
  end
43
74
 
44
75
  @block = block
@@ -46,20 +77,45 @@ class Clive
46
77
 
47
78
  # Runs the block that was given with an argument
48
79
  #
49
- # @param [String] arg argument to pass to the block
50
- def run(arg)
51
- @block.call(arg)
80
+ # @param [Array] args arguments to pass to the block
81
+ # @raise [InvalidArgument] only if +args+ is an array of acceptable inputs
82
+ # and a match is not found.
83
+ def run(args)
84
+ if @args[0].is_a? Hash
85
+ args = Clive::Array.new(@args.collect {|i| !i[:optional]}).optimise_fill(args)
86
+ else # list
87
+ unless @args.include? args[0]
88
+ raise InvalidArgument.new(args)
89
+ end
90
+ end
91
+ @block.call(*args)
92
+ end
93
+
94
+ # @param [Boolean] optional whether to include optional arguments
95
+ # @return [Integer] number of arguments this takes
96
+ def arg_num(optional)
97
+ if @args[0].is_a? Hash
98
+ @args.find_all {|i| i[:optional] == optional }.size
99
+ else
100
+ 1
101
+ end
52
102
  end
53
103
 
54
104
  # @return [String] summary for help
55
105
  def summary(width=30, prepend=5)
56
106
  n = names_to_strings.join(', ')
57
- if @optional
58
- n << " [#{@arg_name}]"
107
+ a = nil
108
+ if @args[0].is_a? Hash
109
+ a = @args.map {|i| i[:name]}.join(' ')
110
+ if @optional
111
+ n << " [#{a}]"
112
+ else
113
+ n << " #{a}"
114
+ end
59
115
  else
60
- n << " #{@arg_name}"
116
+ n << " {" << @args.join(', ') << "}"
61
117
  end
62
-
118
+
63
119
  spaces = width-n.length
64
120
  spaces = 1 if spaces < 1
65
121
  s = spaces(spaces)
@@ -1,16 +1,17 @@
1
1
  class Clive
2
2
 
3
+ # @abstract Subclass and override {#initialize} and {#run} to create a new Option class.
3
4
  class Option
4
5
  attr_accessor :names, :desc, :block
5
6
 
6
- def initialize(desc, *names, &block)
7
- @names = names
8
- @desc = desc
9
- @block = block
7
+ def initialize(*args, &block)
8
+ # assign name and description
9
+ # @block = block
10
10
  end
11
11
 
12
12
  def run
13
- @block.call
13
+ # call the block!
14
+ # @block.call
14
15
  end
15
16
 
16
17
  def summary(width=30, prepend=5)
@@ -22,7 +23,8 @@ class Clive
22
23
  "#{p}#{n}#{s}#{@desc}"
23
24
  end
24
25
 
25
- # Convert the names to strings, depending on length
26
+ # Convert the names to strings, if name is single character appends
27
+ # +-+, else appends +--+.
26
28
  #
27
29
  # @param [Boolean] bool whether to add [no-] to long
28
30
  #
@@ -69,6 +71,7 @@ class Clive
69
71
  r
70
72
  end
71
73
 
74
+ # Compare options based on Option#sort_name
72
75
  def <=>(other)
73
76
  self.sort_name <=> other.sort_name
74
77
  end
@@ -11,13 +11,17 @@ class Clive
11
11
  # ["Value", "--verbose", "-r"]
12
12
  #
13
13
  class Tokens < Array
14
-
14
+
15
15
  TOKEN_KEYS = [:word, :short, :long]
16
16
 
17
17
  # Create a new Tokens instance. Pass either an array of tokens
18
18
  # or a plain array, they will be converted correctly.
19
19
  #
20
- # @param [::Array]
20
+ # @param [Array] args
21
+ # pass either
22
+ # ["command", "--flag"]
23
+ # # or
24
+ # [[:word, "command"], [:long, "flag"]]
21
25
  # @return [Tokens]
22
26
  #
23
27
  def initialize(args=[])
@@ -51,7 +55,10 @@ class Clive
51
55
  arr
52
56
  end
53
57
 
54
- # Creates an array of tokens based on +self+
58
+ # Creates an array of tokens based on +self+.
59
+ # Strings beginning with a -, eg. -n become [:short, "n"].
60
+ # Strings beginning with --, eg. --verbose become [:long, "verbose"].
61
+ # Strings which begin with neither become [:word, "value"].
55
62
  #
56
63
  # @return [::Array] the tokens that are held
57
64
  def tokens
@@ -78,10 +85,12 @@ class Clive
78
85
  t
79
86
  end
80
87
 
88
+ # @see #tokens
81
89
  def self.to_tokens(arr)
82
90
  Tokens.new(arr).tokens
83
91
  end
84
92
 
93
+ # @see #array
85
94
  def self.to_array(tokens)
86
95
  Tokens.new(tokens).array
87
96
  end
@@ -48,6 +48,7 @@ c = Clive.new do
48
48
 
49
49
  end
50
50
 
51
+ p ARGV
51
52
  p c.parse(ARGV)
52
53
  p options
53
54
 
@@ -1,10 +1,13 @@
1
+ require 'duvet'
2
+ Duvet.start :filter => 'clive/lib'
3
+
1
4
  require 'test/unit'
2
5
  require 'shoulda'
3
6
  require 'rr'
4
7
 
5
8
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
9
  $LOAD_PATH.unshift(File.dirname(__FILE__))
7
- require 'clive'
10
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'clive')
8
11
 
9
12
  class Test::Unit::TestCase
10
13
  include RR::Adapters::TestUnit
@@ -155,6 +155,12 @@ class TestClive < Test::Unit::TestCase
155
155
  assert_equal ["argument"], args
156
156
  end
157
157
 
158
+ should "recognise a quoted argument" do
159
+ c = Clive.new {}
160
+ args = c.parse ['a quoted argument']
161
+ assert_equal ["a quoted argument"], args
162
+ end
163
+
158
164
  should "recognise multiple arguments" do
159
165
  c = Clive.new do
160
166
  bool(:v, :verbose, "Run verbosely") {}
@@ -6,7 +6,7 @@ class TestFlag < Test::Unit::TestCase
6
6
 
7
7
  setup do
8
8
  @c = Clive.new do
9
- flag(:t, :type, "TEXT", "Change type to TYPE") {|i| $stdout.puts(i)}
9
+ flag(:t, :type, "TEXT", "Change type to TYPE") {|i| puts(i)}
10
10
  end
11
11
  end
12
12
 
@@ -15,14 +15,14 @@ class TestFlag < Test::Unit::TestCase
15
15
  @c.parse(["--type", "text"])
16
16
  end
17
17
 
18
- should "have an arg name" do
19
- assert_equal "TEXT", @c.flags["t"].arg_name
18
+ should "have a arguments" do
19
+ r = {:name => "TEXT", :optional => false, :type => String}
20
+ assert_equal r, @c.flags["t"].args[0]
20
21
  end
21
22
 
22
23
  should "not be optional" do
23
- assert_equal false, @c.flags["t"].optional
24
+ assert_equal false, @c.flags["t"].args[0][:optional]
24
25
  end
25
-
26
26
  end
27
27
 
28
28
  context "A new flag with default" do
@@ -37,11 +37,12 @@ class TestFlag < Test::Unit::TestCase
37
37
  end
38
38
 
39
39
  should "have an arg name" do
40
- assert_equal "TEXT", @c.flags["t"].arg_name
40
+ r = {:name => "TEXT", :optional => true, :type => String}
41
+ assert_equal r, @c.flags["t"].args[0]
41
42
  end
42
43
 
43
44
  should "be optional" do
44
- assert_equal true, @c.flags["t"].optional
45
+ assert_equal true, @c.flags["t"].args[0][:optional]
45
46
  end
46
47
 
47
48
  should "pass an argument to block" do
@@ -55,4 +56,105 @@ class TestFlag < Test::Unit::TestCase
55
56
  end
56
57
  end
57
58
 
59
+ context "A new flag with multiple arguments" do
60
+
61
+ setup do
62
+ @c = Clive.new do
63
+ flag(:s, :send, "FROM TO", "Send the message from FROM to TO") do |from, to|
64
+ puts from
65
+ puts to
66
+ end
67
+ end
68
+ end
69
+
70
+ should "pass two arguments to the block" do
71
+ mock($stdout).puts("John")
72
+ mock($stdout).puts("Dave")
73
+ @c.parse(["--send", "John", "Dave"])
74
+ end
75
+
76
+ should "have a useful summary" do
77
+ s = "-s, --send FROM TO Send the message from FROM to TO"
78
+ assert_equal s, @c.flags['s'].summary(0, 0)
79
+ end
80
+
81
+ should "work for more than two arguments" do
82
+ mock($stdout).puts("from: Josh, to: John, via: Hong Kong, because: It's far away")
83
+ c = Clive.new do
84
+ flag(:s, :send, "FROM TO VIA BECAUSE", "Send the message...etc") do |f, t, v, b|
85
+ puts "from: #{f}, to: #{t}, via: #{v}, because: #{b}"
86
+ end
87
+ end
88
+ c.parse ['-s', 'Josh', 'John', "Hong Kong", "It's far away"]
89
+ end
90
+
91
+ context "and optional arguments" do
92
+ setup do
93
+ @c = Clive.new do
94
+ flag(:s, :send, "FROM [VIA] TO [BECAUSE]", "Send the message...etc") do |f, v, t, b|
95
+ s = "from: #{f}"
96
+ s << ", via: #{v}" if v
97
+ s << ", to: #{t}"
98
+ s << ", because: #{b}" if b
99
+ puts s
100
+ end
101
+ end
102
+ end
103
+
104
+ should "correctly parse argument string" do
105
+ r = [
106
+ {:name => "FROM", :optional => false, :type => String},
107
+ {:name => "VIA", :optional => true, :type => String},
108
+ {:name => "TO", :optional => false, :type => String},
109
+ {:name => "BECAUSE", :optional => true, :type => String}
110
+ ]
111
+ assert_equal r, @c.flags[:s].args
112
+ end
113
+
114
+ should "give all arguments when found in input" do
115
+ mock($stdout).puts("from: Josh, via: Hong Kong, to: John, because: It's far away")
116
+ @c.parse ["-s", "Josh", "Hong Kong", "John", "It's far away"]
117
+ end
118
+
119
+ should "try to give only needed arguments if some missing" do
120
+ mock($stdout).puts("from: Josh, to: John")
121
+ @c.parse ["-s", "Josh", "John"]
122
+ end
123
+
124
+ should "fill optional arguments starting from left" do
125
+ mock($stdout).puts("from: Josh, via: Hong Kong, to: John")
126
+ @c.parse ["-s", "Josh", "Hong Kong", "John"]
127
+ end
128
+
129
+ end
130
+ end
131
+
132
+ context "A new flag with a list of acceptable arguments" do
133
+
134
+ setup do
135
+ @c = Clive.new do
136
+ flag(:t, :type, ["large", "medium", "small"], "Choose the type to use") {}
137
+ end
138
+ end
139
+
140
+ should "raise error when correct argument not found" do
141
+ assert_raise Clive::InvalidArgument do
142
+ @c.parse(["--type", "apple"])
143
+ end
144
+ end
145
+
146
+ should "not raise error when correct argument given" do
147
+ @c.parse(["--type", "large"])
148
+ end
149
+
150
+ should "make list arguments" do
151
+ r = ["large", "medium", "small"]
152
+ assert_equal r, @c.flags['t'].args
153
+ end
154
+
155
+ should "have a useful summary" do
156
+ s = "-t, --type {large, medium, small} Choose the type to use"
157
+ assert_equal s, @c.flags['t'].summary(0, 0)
158
+ end
159
+ end
58
160
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clive
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 1
10
- version: 0.3.1
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Hawxwell
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-21 00:00:00 +01:00
18
+ date: 2010-09-19 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency