shellopts 1.0.0 → 2.0.0.pre.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+
2
+ module ShellOpts
3
+ # Specialization of Array for arguments lists. Args extends Array with a
4
+ # #extract and an #expect method to extract elements from the array. The
5
+ # methods call #error() in response to errors
6
+ class Args < Array
7
+ def initialize(shellopts, *args)
8
+ @shellopts = shellopts
9
+ super(*args)
10
+ end
11
+
12
+ # Remove and return elements from beginning of the array. If
13
+ # +count_or_range+ is a number, that number of elements will be returned.
14
+ # If the count is one, a simple value is returned instead of an array. If
15
+ # the count is negative, the elements will be removed from the end of the
16
+ # array. If +count_or_range+ is a range, the number of elements returned
17
+ # will be in that range. The range can't contain negative numbers #expect
18
+ # calls #error() if there's is not enough elements in the array to satisfy
19
+ # the request
20
+ def extract(count_or_range, message = nil)
21
+ if count_or_range.is_a?(Range)
22
+ range = count_or_range
23
+ range.min <= self.size or inoa(message)
24
+ n_extract = [self.size, range.max].min
25
+ n_extend = range.max > self.size ? range.max - self.size : 0
26
+ r = self.shift(n_extract) + Array.new(n_extend)
27
+ else
28
+ count = count_or_range
29
+ self.size >= count.abs or inoa(message)
30
+ start = count >= 0 ? 0 : size + count
31
+ r = slice!(start, count.abs)
32
+ r.size == 0 ? nil : (r.size == 1 ? r.first : r)
33
+ end
34
+ end
35
+
36
+ # As extract except it doesn't allow negative counts and that the array is
37
+ # expect to be emptied by the operation
38
+ def expect(count_or_range, message = nil)
39
+ count_or_range === self.size or inoa(message)
40
+ extract(count_or_range) # Can't fail
41
+ end
42
+
43
+ private
44
+ def inoa(message = nil)
45
+ raise ShellOpts::UserError, message || "Illegal number of arguments"
46
+ end
47
+ end
48
+ end
@@ -7,17 +7,17 @@ module ShellOpts
7
7
 
8
8
  # Optional sub-command (Ast::Command). Initially nil but assigned by the
9
9
  # parser
10
- attr_accessor :command
10
+ attr_accessor :subcommand
11
11
 
12
12
  def initialize(grammar, name)
13
13
  super(grammar, name)
14
14
  @options = []
15
- @command = nil
15
+ @subcommand = nil
16
16
  end
17
17
 
18
18
  # Array of option or command tuples
19
19
  def values
20
- (options + (Array(command || []))).map { |node| node.to_tuple }
20
+ (options + (Array(subcommand || []))).map { |node| node.to_tuple }
21
21
  end
22
22
 
23
23
  # :nocov:
@@ -26,10 +26,10 @@ module ShellOpts
26
26
  yield if block_given?
27
27
  puts "options:"
28
28
  indent { options.each { |opt| opt.dump } }
29
- print "command:"
30
- if command
29
+ print "subcommand:"
30
+ if subcommand
31
31
  puts
32
- indent { command.dump }
32
+ indent { subcommand.dump }
33
33
  else
34
34
  puts "nil"
35
35
  end
@@ -23,7 +23,7 @@ module ShellOpts
23
23
  end
24
24
 
25
25
  # Return either a value (option value), an array of values (command), or
26
- # nil (option without a value). Should be defined in sub-classes
26
+ # nil (option without a value). It must be defined in sub-classes of Ast::Node
27
27
  def values() raise end
28
28
 
29
29
  # :nocov:
@@ -8,12 +8,12 @@ require 'shellopts/grammar/program.rb'
8
8
  module ShellOpts
9
9
  module Grammar
10
10
  # Compiles an option definition string and returns a Grammar::Program
11
- # object. program_name is the name of the program and source is the
11
+ # object. name is the name of the program and source is the
12
12
  # option definition string
13
- def self.compile(program_name, source)
14
- program_name.is_a?(String) or raise Compiler::Error, "Expected String argument, got #{program_name.class}"
13
+ def self.compile(name, source)
14
+ name.is_a?(String) or raise Compiler::Error, "Expected String argument, got #{name.class}"
15
15
  source.is_a?(String) or raise Compiler::Error, "Expected String argument, got #{source.class}"
16
- Compiler.new(program_name, source).call
16
+ Compiler.new(name, source).call
17
17
  end
18
18
 
19
19
  # Service object for compiling an option definition string. Returns a
@@ -26,14 +26,14 @@ module ShellOpts
26
26
  class Error < RuntimeError; end
27
27
 
28
28
  # Initialize a Compiler object. source is the option definition string
29
- def initialize(program_name, source)
30
- @program_name, @tokens = program_name, source.split(/\s+/).reject(&:empty?)
29
+ def initialize(name, source)
30
+ @name, @tokens = name, source.split(/\s+/).reject(&:empty?)
31
31
 
32
- # @commands_by_path is an hash from command-path to Command or Program
32
+ # @subcommands_by_path is an hash from subcommand-path to Command or Program
33
33
  # object. The top level Program object has nil as its path.
34
- # @commands_by_path is used to check for uniqueness of commands and to
35
- # link sub-commands to their parents
36
- @commands_by_path = {}
34
+ # @subcommands_by_path is used to check for uniqueness of subcommands and to
35
+ # link sub-subcommands to their parents
36
+ @subcommands_by_path = {}
37
37
  end
38
38
 
39
39
  def call
@@ -49,30 +49,26 @@ module ShellOpts
49
49
  # Returns the current token and advance to the next token
50
50
  def next_token() @tokens.shift end
51
51
 
52
- def error(msg) # Just a shorthand. Unrelated to ShellOpts.error
53
- raise Compiler::Error.new(msg)
54
- end
55
-
56
52
  def compile_program
57
- program = @commands_by_path[nil] = Grammar::Program.new(@program_name, compile_options)
53
+ program = @subcommands_by_path[nil] = Grammar::Program.new(@name, compile_options)
58
54
  while curr_token && curr_token != "--"
59
- compile_command
55
+ compile_subcommand
60
56
  end
61
57
  program.args.concat(@tokens[1..-1]) if curr_token
62
58
  program
63
59
  end
64
60
 
65
- def compile_command
61
+ def compile_subcommand
66
62
  path = curr_token[0..-2]
67
63
  ident_list = compile_ident_list(path, ".")
68
64
  parent_path = ident_list.size > 1 ? ident_list[0..-2].join(".") : nil
69
65
  name = ident_list[-1]
70
66
 
71
- parent = @commands_by_path[parent_path] or
72
- error "No such command: #{parent_path.inspect}"
73
- !@commands_by_path.key?(path) or error "Duplicate command: #{path.inspect}"
67
+ parent = @subcommands_by_path[parent_path] or
68
+ raise Compiler::Error, "No such subcommand: #{parent_path.inspect}"
69
+ !@subcommands_by_path.key?(path) or raise Compiler::Error, "Duplicate subcommand: #{path.inspect}"
74
70
  next_token
75
- @commands_by_path[path] = Grammar::Command.new(parent, name, compile_options)
71
+ @subcommands_by_path[path] = Grammar::Command.new(parent, name, compile_options)
76
72
  end
77
73
 
78
74
  def compile_options
@@ -81,7 +77,7 @@ module ShellOpts
81
77
  option_list << compile_option
82
78
  end
83
79
  dup = option_list.map(&:names).flatten.find_dup and
84
- error "Duplicate option name: #{dup.inspect}"
80
+ raise Compiler::Error, "Duplicate option name: #{dup.inspect}"
85
81
  option_list
86
82
  end
87
83
 
@@ -102,7 +98,7 @@ module ShellOpts
102
98
  long_names = []
103
99
  ident_list = compile_ident_list(names, ",")
104
100
  (dup = ident_list.find_dup).nil? or
105
- error "Duplicate identifier #{dup.inspect} in #{curr_token.inspect}"
101
+ raise Compiler::Error, "Duplicate identifier #{dup.inspect} in #{curr_token.inspect}"
106
102
  ident_list.each { |ident|
107
103
  if ident.size == 1
108
104
  short_names << "-#{ident}"
@@ -115,13 +111,15 @@ module ShellOpts
115
111
  Grammar::Option.new(short_names, long_names, flags, label)
116
112
  end
117
113
 
118
- # Compile list of option names or a command path
114
+ # Compile list of option names or a subcommand path
119
115
  def compile_ident_list(ident_list_str, sep)
120
116
  ident_list_str.split(sep, -1).map { |str|
121
- !str.empty? or error "Empty identifier in #{curr_token.inspect}"
122
- !str.start_with?("-") or error "Identifier can't start with '-' in #{curr_token.inspect}"
117
+ !str.empty? or
118
+ raise Compiler::Error, "Empty identifier in #{curr_token.inspect}"
119
+ !str.start_with?("-") or
120
+ raise Compiler::Error, "Identifier can't start with '-' in #{curr_token.inspect}"
123
121
  str !~ /([^\w\d#{sep}-])/ or
124
- error "Illegal character #{$1.inspect} in #{curr_token.inspect}"
122
+ raise Compiler::Error, "Illegal character #{$1.inspect} in #{curr_token.inspect}"
125
123
  str
126
124
  }
127
125
  end
@@ -0,0 +1,15 @@
1
+
2
+ require 'shellopts/idr.rb'
3
+
4
+ module ShellOpts
5
+ module Idr
6
+ # Generates an Idr::Program from a ShellOpts object
7
+ def self.generate(shellopts)
8
+ Idr::Program.new(shellopts)
9
+ end
10
+ end
11
+ end
12
+
13
+
14
+
15
+
@@ -11,32 +11,50 @@ module ShellOpts
11
11
  # Name of command (String). Name doesn't include the exclamation point ('!')
12
12
  attr_reader :name
13
13
 
14
- # Hash from option names (both short and long names) to option. This
15
- # means an option can occur more than once as the hash value
16
- attr_reader :options
17
-
18
- # Sub-commands of this command. Is a hash from sub-command name to command object
19
- attr_reader :commands
14
+ # Same as #name. TODO Define in Grammar::Node instead
15
+ alias :key_name :name
20
16
 
21
17
  # List of options in declaration order
22
18
  attr_reader :option_list
23
19
 
24
20
  # List of commands in declaration order
25
- attr_reader :command_list
21
+ attr_reader :subcommand_list
22
+
23
+ # Multihash from option key or names (both short and long names) to option. This
24
+ # means an option can occur more than once as the hash value
25
+ def options()
26
+ @option_multihash ||= @option_list.flat_map { |option|
27
+ option.identifiers.map { |ident| [ident, option] }
28
+ }.to_h
29
+ end
30
+
31
+ # Sub-commands of this command. Is a multihash from sub-command key or
32
+ # name to command object. Lazily constructed because subcommands are added
33
+ # after initialization
34
+ def subcommands()
35
+ @subcommand_multihash ||= @subcommand_list.flat_map { |subcommand|
36
+ subcommand.identifiers.map { |name| [name, subcommand] }
37
+ }.to_h
38
+ end
26
39
 
27
40
  # Initialize a Command object. parent is the parent Command object or nil
28
41
  # if this is the root object. name is the name of the command (without
29
42
  # the exclamation mark), and option_list a list of Option objects
30
43
  def initialize(parent, name, option_list)
31
- super("#{name}!".to_sym)
32
- @name = name
44
+ super("#{name}!".to_sym, name)
33
45
  parent.attach(self) if parent
34
46
  @option_list = option_list
35
- @options = @option_list.flat_map { |opt| opt.names.map { |name| [name, opt] } }.to_h
36
- @commands = {}
37
- @command_list = []
47
+ @subcommand_list = []
38
48
  end
39
49
 
50
+ # Return key for the identifier
51
+ def identifier2key(ident)
52
+ options[ident]&.key || subcommands[ident]&.key
53
+ end
54
+
55
+ # Return list of identifiers for the command
56
+ def identifiers() [key, name] end
57
+
40
58
  # :nocov:
41
59
  def dump(&block)
42
60
  puts "#{key.inspect}"
@@ -46,17 +64,16 @@ module ShellOpts
46
64
  yield if block_given?
47
65
  puts "options:"
48
66
  indent { option_list.each { |opt| opt.dump } }
49
- puts "commands: "
50
- indent { command_list.each { |cmd| cmd.dump } }
67
+ puts "subcommands: "
68
+ indent { subcommand_list.each { |cmd| cmd.dump } }
51
69
  }
52
70
  end
53
71
  # :nocov:
54
72
 
55
73
  protected
56
- def attach(command)
57
- command.instance_variable_set(:@parent, self)
58
- @commands[command.name] = command
59
- @command_list << command
74
+ def attach(subcommand)
75
+ subcommand.instance_variable_set(:@parent, self)
76
+ @subcommand_list << subcommand
60
77
  end
61
78
  end
62
79
  end
@@ -10,14 +10,22 @@ module ShellOpts
10
10
  # Key (Symbol) of node. Unique within the enclosing command
11
11
  attr_reader :key
12
12
 
13
- def initialize(key)
14
- @key = key
13
+ # Name of node. The name of an option is without the prefixed '-' or
14
+ # '--', the name of a command is without the suffixed '!'. Note that name
15
+ # collisions can happen between options and commands names
16
+ attr_reader :name
17
+
18
+ def initialize(key, name)
19
+ @key, @name = key, name
15
20
  end
16
21
 
17
22
  # :nocov:
18
23
  def dump(&block)
19
24
  puts key.inspect
20
- indent { yield } if block_given?
25
+ indent {
26
+ puts "name: #{name.inspect}"
27
+ yield if block_given?
28
+ }
21
29
  end
22
30
  # :nocov:
23
31
  end
@@ -10,6 +10,9 @@ module ShellOpts
10
10
  # List of long names (incl. '--')
11
11
  attr_reader :long_names
12
12
 
13
+ # Name of the key attribute (eg. if key is :all then key_name is '--all'
14
+ attr_reader :key_name
15
+
13
16
  # List of flags (Symbol)
14
17
  def flags() @flags.keys end
15
18
 
@@ -23,7 +26,9 @@ module ShellOpts
23
26
  # there's no :string flag, it's status is inferred. label is the optional
24
27
  # informal name of the option argument (eg. 'FILE') or nil if not present
25
28
  def initialize(short_names, long_names, flags, label = nil)
26
- super((long_names.first || short_names.first).sub(/^-+/, "").to_sym)
29
+ @key_name = long_names.first || short_names.first
30
+ name = @key_name.sub(/^-+/, "")
31
+ super(name.to_sym, name)
27
32
  @short_names, @long_names = short_names, long_names
28
33
  @flags = flags.map { |flag| [flag, true] }.to_h
29
34
  @label = label
@@ -32,6 +37,12 @@ module ShellOpts
32
37
  # Array of option names with short names first and then the long names
33
38
  def names() @short_names + @long_names end
34
39
 
40
+ # Array of names and the key
41
+ def identifiers() names + [key] end
42
+
43
+ # Return true if +ident+ is equal to any name or to key
44
+ def match?(ident) names.include?(ident) || ident == key end
45
+
35
46
  # Flag query methods. Returns true if the flag is present and otherwise nil
36
47
  def repeated?() @flags[:repeated] || false end
37
48
  def argument?() @flags[:argument] || false end
@@ -17,7 +17,7 @@ module ShellOpts
17
17
  def usage
18
18
  (
19
19
  render_options(option_list) +
20
- commands.values.map { |cmd| render_command(cmd) } +
20
+ subcommand_list.map { |cmd| render_subcommand(cmd) } +
21
21
  args
22
22
  ).flatten.join(" ")
23
23
  end
@@ -32,9 +32,9 @@ module ShellOpts
32
32
  # :nocov:
33
33
 
34
34
  private
35
- def render_command(command)
36
- [command.name] + render_options(command.option_list) +
37
- command.commands.values.map { |cmd| render_command(cmd) }.flatten
35
+ def render_subcommand(subcommand)
36
+ [subcommand.name] + render_options(subcommand.option_list) +
37
+ subcommand.subcommand_list.map { |cmd| render_subcommand(cmd) }.flatten
38
38
  end
39
39
 
40
40
  def render_options(options)
@@ -0,0 +1,236 @@
1
+
2
+ module ShellOpts
3
+ # Idr models the Internal Data Representation of a program. It is the native
4
+ # representation of a command
5
+ #
6
+ # The IDR should ideally be completely detached from the compile-time grammar
7
+ # and AST but they are only hidden from view in this implementation. Create
8
+ # a Shellopts object instead to access the compiler data
9
+ #
10
+ module Idr
11
+ # Base class for the Idr class hierarchy. It is constructed from an Ast
12
+ # object by #generate. Node is modelled as an element of a hash with a key
13
+ # and a value. Options have their (optional) argument as value while
14
+ # commands use +self+ as value
15
+ class Node
16
+ # Parent node. nil for the top-level Program object
17
+ attr_reader :parent
18
+
19
+ # Unique key (within context) for the option or command. nil for the
20
+ # top-level Program object
21
+ attr_reader :key
22
+
23
+ # Name of command or option as used on the command line
24
+ attr_reader :name
25
+
26
+ # Value of node. This can be a simple value (String, Integer, or Float),
27
+ # an Array of values, or a Idr::Command object. Note that the value of a
28
+ # Command object is the object itself
29
+ #
30
+ # Repeated options are implemented as an Array with one element for each
31
+ # use of the option. The element is nil if the option doesn't take
32
+ # arguments or if an optional argument is missing.
33
+ attr_reader :value
34
+
35
+ # The top-level Program object
36
+ def program() @program ||= (parent&.program || self) end
37
+
38
+ protected
39
+ # Copy arguments into instance variables
40
+ def initialize(parent, ast, key, name, value)
41
+ @parent, @ast, @key, @name, @value = parent, ast, key, name, value
42
+ end
43
+
44
+ # The AST node for this Idr object
45
+ attr_reader :ast
46
+
47
+ # Shorthand to the grammar node for this Idr object
48
+ def grammar() @ast.grammar end
49
+ end
50
+
51
+ # Base class for Options
52
+ class Option < Node
53
+ end
54
+
55
+ class SimpleOption < Option
56
+ protected
57
+ # Initialize with defauls from the Ast. +value+ is set to true if option
58
+ # doesn't take an argument
59
+ def initialize(parent, ast)
60
+ value = ast.grammar.argument? ? ast.value : true
61
+ super(parent, ast, ast.key, ast.name, value)
62
+ end
63
+ end
64
+
65
+ # An OptionGroup models repeated options collapsed into a single key. The
66
+ # name of the group should be set to the name of the key (eg. '--all' if
67
+ # the key is :all)
68
+ class OptionGroup < Option
69
+ # Array of names of the options
70
+ attr_reader :names
71
+
72
+ # Array of values of the options
73
+ alias :values :value
74
+
75
+ # Name is set to the key name and value to an array of option values
76
+ def initialize(parent, key, name, options)
77
+ @names = options.map(&:name)
78
+ super(parent, nil, key, name, options.map(&:value))
79
+ end
80
+ end
81
+
82
+ class Command < Node
83
+ # Hash from key to options with repeated option_list collapsed into a
84
+ # option group. It also include an entry for the subcommand. Options are
85
+ # ordered by first use on the command line. The command entry will always
86
+ # be last
87
+ attr_reader :options
88
+
89
+ # List of command line options in the same order as on the command line
90
+ attr_reader :option_list
91
+
92
+ # Subcommand object. Possibly nil
93
+ attr_reader :subcommand
94
+
95
+ # True if ident is declared
96
+ def declared?(ident) option?(ident) || subcommand?(ident) end
97
+
98
+ # True if ident is declared as an option
99
+ def option?(ident) grammar.options.key?(ident) end
100
+
101
+ # True if ident is declared as a command
102
+ def subcommand?(ident) grammar.subcommands.key?(ident) end
103
+
104
+ # True if ident is present
105
+ def key?(ident)
106
+ declared?(ident) or raise InternalError, "Undefined identifier: #{ident.inspect}"
107
+ key = grammar.identifier2key(ident)
108
+ @options.key?(key)
109
+ end
110
+
111
+ # Value of ident. Repeated options are collapsed into an OptionGroup object
112
+ def [](ident)
113
+ declared?(ident) or raise InternalError, "Undefined identifier: #{ident.inspect}"
114
+ key = grammar.identifier2key(ident)
115
+ if @options.key?(key)
116
+ @options[key].value
117
+ elsif option?(key)
118
+ false
119
+ else
120
+ nil
121
+ end
122
+ end
123
+
124
+ # Apply defaults recursively. Values can be lambdas that will be evaluated to
125
+ # get the default value. TODO
126
+ def apply(defaults = {}) raise InternalError, "Not implemented" end
127
+
128
+ # Return options and command as an array
129
+ def to_a() @ast.values end
130
+
131
+ # Return options and command as a hash. The hash also define the
132
+ # singleton method #subcommand that returns the key of the subcommand
133
+ #
134
+ # +key_type+ controls the type of keys used: +:key+ (the default) use the
135
+ # symbolic key, +:name+ use #name. Note that using +:name+ can cause
136
+ # name collisions between option and command names
137
+ #
138
+ # +aliases+ maps from key to replacement key (which could be any object).
139
+ # +aliases+ can be used to avoid name collisions between options and
140
+ # commands when using key_format: :name
141
+ #
142
+ # IDEA: Add a singleton methods to the hash with #name, #usage, etc.
143
+ #
144
+ def to_h(key_type: ::ShellOpts.default_key_type, aliases: {})
145
+ keys = map_keys(key_type, aliases)
146
+ value = {}
147
+ value.define_singleton_method(:subcommand) { nil }
148
+ options.values.each { |opt| # includes subcommand
149
+ key = keys[opt.key]
150
+ case opt
151
+ when Option
152
+ value[key] = opt.value
153
+ when Command
154
+ value[key] = opt.value.to_h(key_type: key_type, aliases: aliases[opt.key] || {})
155
+ value.define_singleton_method(:subcommand) { key } # Redefine
156
+ else
157
+ # :nocov:
158
+ raise InternalError, "Oops"
159
+ # :nocov:
160
+ end
161
+ }
162
+ value
163
+ end
164
+
165
+ # Return options and command as a struct
166
+ def to_struct(key_type: ::ShellOpts.default_key_type, aliases: {})
167
+ OptionStruct.new(self, key_type, aliases)
168
+ end
169
+
170
+ protected
171
+ # Initialize an Idr::Command object and all dependent objects
172
+ def initialize(parent, ast)
173
+ super(parent, ast, ast.key, ast.name, self)
174
+ @option_list = ast.options.map { |node| SimpleOption.new(self, node) }
175
+ @subcommand = Command.new(self, ast.subcommand) if ast.subcommand
176
+ @options = @option_list.group_by { |option| option.key }.map { |key, option_list|
177
+ option =
178
+ if ast.grammar.options[key].repeated?
179
+ OptionGroup.new(self, key, ast.grammar.options[key].key_name, option_list)
180
+ else
181
+ option_list.first
182
+ end
183
+ [key, option]
184
+ }.to_h
185
+ @options[subcommand.key] = @subcommand if @subcommand
186
+ end
187
+
188
+ # Internal-key to used-key map. Checks for reserved words and
189
+ # name-collisions
190
+ def map_keys(key_type, aliases, reserved_words = [])
191
+ keys = {}
192
+ used_keys = {}
193
+ (grammar.option_list + grammar.subcommand_list).each { |node|
194
+ internal_key = node.key
195
+ key = aliases[internal_key] || (key_type == :name ? node.name.to_sym : internal_key)
196
+ !reserved_words.include?(key) or
197
+ raise ::ShellOpts::ConversionError, "'#{key}' is a reserved word"
198
+ !used_keys.key?(key) or
199
+ raise ::ShellOpts::ConversionError, "Name collision between '--#{key}' and '#{key}!'"
200
+ keys[internal_key] = key
201
+ used_keys[key] = true
202
+ }
203
+ keys
204
+ end
205
+ end
206
+
207
+ class Program < Command
208
+ # Name of program
209
+ def name() @shellopts.name end
210
+ def name=(name) @shellopts.name = name end
211
+
212
+ # Usage string
213
+ def usage() @shellopts.usage end
214
+ def usage=(usage) @shellopts.usage = usage end
215
+
216
+ # #key is nil for the top-level Program object
217
+ def key() nil end
218
+
219
+ # Remaining command line arguments
220
+ def args() @shellopts.args end
221
+
222
+ # Initialize the top-level Idr::Program object
223
+ def initialize(shellopts)
224
+ @shellopts = shellopts
225
+ super(nil, shellopts.ast)
226
+ end
227
+
228
+ # Emit error message and a usage description before exiting with status 1
229
+ def error(*args) @shellopts.error(*error_messages) end
230
+
231
+ # Emit error message before exiting with status 1
232
+ def fail(*args) @shellopts.fail(*error_messages) end
233
+ end
234
+ end
235
+ end
236
+