engineyard 1.0.0 → 1.0.1
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.
- data/lib/engineyard/cli.rb +11 -2
- data/lib/engineyard/model/environment.rb +36 -3
- data/lib/engineyard/model/instance.rb +1 -1
- data/lib/engineyard/thor.rb +50 -68
- data/lib/engineyard/version.rb +1 -1
- data/spec/ey/deploy_spec.rb +66 -0
- metadata +23 -36
- data/lib/engineyard/vendor/thor.rb +0 -270
- data/lib/engineyard/vendor/thor/actions.rb +0 -297
- data/lib/engineyard/vendor/thor/actions/create_file.rb +0 -105
- data/lib/engineyard/vendor/thor/actions/directory.rb +0 -93
- data/lib/engineyard/vendor/thor/actions/empty_directory.rb +0 -134
- data/lib/engineyard/vendor/thor/actions/file_manipulation.rb +0 -229
- data/lib/engineyard/vendor/thor/actions/inject_into_file.rb +0 -104
- data/lib/engineyard/vendor/thor/base.rb +0 -540
- data/lib/engineyard/vendor/thor/core_ext/file_binary_read.rb +0 -9
- data/lib/engineyard/vendor/thor/core_ext/hash_with_indifferent_access.rb +0 -75
- data/lib/engineyard/vendor/thor/core_ext/ordered_hash.rb +0 -100
- data/lib/engineyard/vendor/thor/error.rb +0 -30
- data/lib/engineyard/vendor/thor/group.rb +0 -271
- data/lib/engineyard/vendor/thor/invocation.rb +0 -180
- data/lib/engineyard/vendor/thor/parser.rb +0 -4
- data/lib/engineyard/vendor/thor/parser/argument.rb +0 -67
- data/lib/engineyard/vendor/thor/parser/arguments.rb +0 -161
- data/lib/engineyard/vendor/thor/parser/option.rb +0 -128
- data/lib/engineyard/vendor/thor/parser/options.rb +0 -164
- data/lib/engineyard/vendor/thor/rake_compat.rb +0 -66
- data/lib/engineyard/vendor/thor/runner.rb +0 -314
- data/lib/engineyard/vendor/thor/shell.rb +0 -83
- data/lib/engineyard/vendor/thor/shell/basic.rb +0 -268
- data/lib/engineyard/vendor/thor/shell/color.rb +0 -108
- data/lib/engineyard/vendor/thor/task.rb +0 -102
- data/lib/engineyard/vendor/thor/util.rb +0 -229
- data/lib/engineyard/vendor/thor/version.rb +0 -3
@@ -1,180 +0,0 @@
|
|
1
|
-
class Thor
|
2
|
-
module Invocation
|
3
|
-
def self.included(base) #:nodoc:
|
4
|
-
base.extend ClassMethods
|
5
|
-
end
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
# This method is responsible for receiving a name and find the proper
|
9
|
-
# class and task for it. The key is an optional parameter which is
|
10
|
-
# available only in class methods invocations (i.e. in Thor::Group).
|
11
|
-
def prepare_for_invocation(key, name) #:nodoc:
|
12
|
-
case name
|
13
|
-
when Symbol, String
|
14
|
-
Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
|
15
|
-
else
|
16
|
-
name
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Make initializer aware of invocations and the initialization args.
|
22
|
-
def initialize(args=[], options={}, config={}, &block) #:nodoc:
|
23
|
-
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
|
24
|
-
@_initializer = [ args, options, config ]
|
25
|
-
super
|
26
|
-
end
|
27
|
-
|
28
|
-
# Receives a name and invokes it. The name can be a string (either "task" or
|
29
|
-
# "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
|
30
|
-
# cannot be guessed by name, it can also be supplied as second argument.
|
31
|
-
#
|
32
|
-
# You can also supply the arguments, options and configuration values for
|
33
|
-
# the task to be invoked, if none is given, the same values used to
|
34
|
-
# initialize the invoker are used to initialize the invoked.
|
35
|
-
#
|
36
|
-
# When no name is given, it will invoke the default task of the current class.
|
37
|
-
#
|
38
|
-
# ==== Examples
|
39
|
-
#
|
40
|
-
# class A < Thor
|
41
|
-
# def foo
|
42
|
-
# invoke :bar
|
43
|
-
# invoke "b:hello", ["José"]
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# def bar
|
47
|
-
# invoke "b:hello", ["José"]
|
48
|
-
# end
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# class B < Thor
|
52
|
-
# def hello(name)
|
53
|
-
# puts "hello #{name}"
|
54
|
-
# end
|
55
|
-
# end
|
56
|
-
#
|
57
|
-
# You can notice that the method "foo" above invokes two tasks: "bar",
|
58
|
-
# which belongs to the same class and "hello" which belongs to the class B.
|
59
|
-
#
|
60
|
-
# By using an invocation system you ensure that a task is invoked only once.
|
61
|
-
# In the example above, invoking "foo" will invoke "b:hello" just once, even
|
62
|
-
# if it's invoked later by "bar" method.
|
63
|
-
#
|
64
|
-
# When class A invokes class B, all arguments used on A initialization are
|
65
|
-
# supplied to B. This allows lazy parse of options. Let's suppose you have
|
66
|
-
# some rspec tasks:
|
67
|
-
#
|
68
|
-
# class Rspec < Thor::Group
|
69
|
-
# class_option :mock_framework, :type => :string, :default => :rr
|
70
|
-
#
|
71
|
-
# def invoke_mock_framework
|
72
|
-
# invoke "rspec:#{options[:mock_framework]}"
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
#
|
76
|
-
# As you noticed, it invokes the given mock framework, which might have its
|
77
|
-
# own options:
|
78
|
-
#
|
79
|
-
# class Rspec::RR < Thor::Group
|
80
|
-
# class_option :style, :type => :string, :default => :mock
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# Since it's not rspec concern to parse mock framework options, when RR
|
84
|
-
# is invoked all options are parsed again, so RR can extract only the options
|
85
|
-
# that it's going to use.
|
86
|
-
#
|
87
|
-
# If you want Rspec::RR to be initialized with its own set of options, you
|
88
|
-
# have to do that explicitely:
|
89
|
-
#
|
90
|
-
# invoke "rspec:rr", [], :style => :foo
|
91
|
-
#
|
92
|
-
# Besides giving an instance, you can also give a class to invoke:
|
93
|
-
#
|
94
|
-
# invoke Rspec::RR, [], :style => :foo
|
95
|
-
#
|
96
|
-
def invoke(name=nil, *args)
|
97
|
-
args.unshift(nil) if Array === args.first || NilClass === args.first
|
98
|
-
task, args, opts, config = args
|
99
|
-
|
100
|
-
object, task = _prepare_for_invocation(name, task)
|
101
|
-
klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
|
102
|
-
|
103
|
-
method_args = []
|
104
|
-
current = @_invocations[klass]
|
105
|
-
|
106
|
-
iterator = proc do |_, task|
|
107
|
-
unless current.include?(task.name)
|
108
|
-
current << task.name
|
109
|
-
task.run(instance, method_args)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
if task
|
114
|
-
args ||= []
|
115
|
-
method_args = args[Range.new(klass.arguments.size, -1)] || []
|
116
|
-
iterator.call(nil, task)
|
117
|
-
else
|
118
|
-
klass.all_tasks.map(&iterator)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Invokes using shell padding.
|
123
|
-
def invoke_with_padding(*args)
|
124
|
-
with_padding { invoke(*args) }
|
125
|
-
end
|
126
|
-
|
127
|
-
protected
|
128
|
-
|
129
|
-
# Configuration values that are shared between invocations.
|
130
|
-
def _shared_configuration #:nodoc:
|
131
|
-
{ :invocations => @_invocations }
|
132
|
-
end
|
133
|
-
|
134
|
-
# This method can receive several different types of arguments and it's then
|
135
|
-
# responsible to normalize them by returning the object where the task should
|
136
|
-
# be invoked and a Thor::Task object.
|
137
|
-
def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
|
138
|
-
if name.is_a?(Thor::Task)
|
139
|
-
task = name
|
140
|
-
elsif task = self.class.all_tasks[name.to_s]
|
141
|
-
object = self
|
142
|
-
else
|
143
|
-
object, task = self.class.prepare_for_invocation(nil, name)
|
144
|
-
task ||= sent_task
|
145
|
-
end
|
146
|
-
|
147
|
-
# If the object was not set, use self and use the name as task.
|
148
|
-
object, task = self, name unless object
|
149
|
-
return object, _validate_task(object, task)
|
150
|
-
end
|
151
|
-
|
152
|
-
# Check if the object given is a Thor class object and get a task object
|
153
|
-
# for it.
|
154
|
-
def _validate_task(object, task) #:nodoc:
|
155
|
-
klass = object.is_a?(Class) ? object : object.class
|
156
|
-
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
157
|
-
|
158
|
-
task ||= klass.default_task if klass.respond_to?(:default_task)
|
159
|
-
task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task)
|
160
|
-
task
|
161
|
-
end
|
162
|
-
|
163
|
-
# Initialize klass using values stored in the @_initializer.
|
164
|
-
def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
|
165
|
-
if object.is_a?(Class)
|
166
|
-
klass = object
|
167
|
-
|
168
|
-
stored_args, stored_opts, stored_config = @_initializer
|
169
|
-
args ||= stored_args.dup
|
170
|
-
opts ||= stored_opts.dup
|
171
|
-
|
172
|
-
config ||= {}
|
173
|
-
config = stored_config.merge(_shared_configuration).merge!(config)
|
174
|
-
[ klass, klass.new(args, opts, config) ]
|
175
|
-
else
|
176
|
-
[ object.class, object ]
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
class Thor
|
2
|
-
class Argument #:nodoc:
|
3
|
-
VALID_TYPES = [ :numeric, :hash, :array, :string ]
|
4
|
-
|
5
|
-
attr_reader :name, :description, :required, :type, :default, :banner
|
6
|
-
alias :human_name :name
|
7
|
-
|
8
|
-
def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil)
|
9
|
-
class_name = self.class.name.split("::").last
|
10
|
-
|
11
|
-
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
|
12
|
-
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
|
13
|
-
|
14
|
-
@name = name.to_s
|
15
|
-
@description = description
|
16
|
-
@required = required || false
|
17
|
-
@type = (type || :string).to_sym
|
18
|
-
@default = default
|
19
|
-
@banner = banner || default_banner
|
20
|
-
|
21
|
-
validate! # Trigger specific validations
|
22
|
-
end
|
23
|
-
|
24
|
-
def usage
|
25
|
-
required? ? banner : "[#{banner}]"
|
26
|
-
end
|
27
|
-
|
28
|
-
def required?
|
29
|
-
required
|
30
|
-
end
|
31
|
-
|
32
|
-
def show_default?
|
33
|
-
case default
|
34
|
-
when Array, String, Hash
|
35
|
-
!default.empty?
|
36
|
-
else
|
37
|
-
default
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
protected
|
42
|
-
|
43
|
-
def validate!
|
44
|
-
raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
|
45
|
-
end
|
46
|
-
|
47
|
-
def valid_type?(type)
|
48
|
-
VALID_TYPES.include?(type.to_sym)
|
49
|
-
end
|
50
|
-
|
51
|
-
def default_banner
|
52
|
-
case type
|
53
|
-
when :boolean
|
54
|
-
nil
|
55
|
-
when :string, :default
|
56
|
-
human_name.upcase
|
57
|
-
when :numeric
|
58
|
-
"N"
|
59
|
-
when :hash
|
60
|
-
"key:value"
|
61
|
-
when :array
|
62
|
-
"one two three"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
end
|
@@ -1,161 +0,0 @@
|
|
1
|
-
class Thor
|
2
|
-
class Arguments #:nodoc:
|
3
|
-
NUMERIC = /(\d*\.\d+|\d+)/
|
4
|
-
|
5
|
-
# Receives an array of args and returns two arrays, one with arguments
|
6
|
-
# and one with switches.
|
7
|
-
#
|
8
|
-
def self.split(args)
|
9
|
-
arguments = []
|
10
|
-
|
11
|
-
args.each do |item|
|
12
|
-
break if item =~ /^-/
|
13
|
-
arguments << item
|
14
|
-
end
|
15
|
-
|
16
|
-
return arguments, args[Range.new(arguments.size, -1)]
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.parse(*args)
|
20
|
-
to_parse = args.pop
|
21
|
-
new(*args).parse(to_parse)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Takes an array of Thor::Argument objects.
|
25
|
-
#
|
26
|
-
def initialize(arguments=[])
|
27
|
-
@assigns, @non_assigned_required = {}, []
|
28
|
-
@switches = arguments
|
29
|
-
|
30
|
-
arguments.each do |argument|
|
31
|
-
if argument.default
|
32
|
-
@assigns[argument.human_name] = argument.default
|
33
|
-
elsif argument.required?
|
34
|
-
@non_assigned_required << argument
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def parse(args)
|
40
|
-
@pile = args.dup
|
41
|
-
|
42
|
-
@switches.each do |argument|
|
43
|
-
break unless peek
|
44
|
-
@non_assigned_required.delete(argument)
|
45
|
-
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
|
46
|
-
end
|
47
|
-
|
48
|
-
check_requirement!
|
49
|
-
@assigns
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def no_or_skip?(arg)
|
55
|
-
arg =~ /^--(no|skip)-([-\w]+)$/
|
56
|
-
$2
|
57
|
-
end
|
58
|
-
|
59
|
-
def last?
|
60
|
-
@pile.empty?
|
61
|
-
end
|
62
|
-
|
63
|
-
def peek
|
64
|
-
@pile.first
|
65
|
-
end
|
66
|
-
|
67
|
-
def shift
|
68
|
-
@pile.shift
|
69
|
-
end
|
70
|
-
|
71
|
-
def unshift(arg)
|
72
|
-
unless arg.kind_of?(Array)
|
73
|
-
@pile.unshift(arg)
|
74
|
-
else
|
75
|
-
@pile = arg + @pile
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def current_is_value?
|
80
|
-
peek && peek.to_s !~ /^-/
|
81
|
-
end
|
82
|
-
|
83
|
-
# Runs through the argument array getting strings that contains ":" and
|
84
|
-
# mark it as a hash:
|
85
|
-
#
|
86
|
-
# [ "name:string", "age:integer" ]
|
87
|
-
#
|
88
|
-
# Becomes:
|
89
|
-
#
|
90
|
-
# { "name" => "string", "age" => "integer" }
|
91
|
-
#
|
92
|
-
def parse_hash(name)
|
93
|
-
return shift if peek.is_a?(Hash)
|
94
|
-
hash = {}
|
95
|
-
|
96
|
-
while current_is_value? && peek.include?(?:)
|
97
|
-
key, value = shift.split(':')
|
98
|
-
hash[key] = value
|
99
|
-
end
|
100
|
-
hash
|
101
|
-
end
|
102
|
-
|
103
|
-
# Runs through the argument array getting all strings until no string is
|
104
|
-
# found or a switch is found.
|
105
|
-
#
|
106
|
-
# ["a", "b", "c"]
|
107
|
-
#
|
108
|
-
# And returns it as an array:
|
109
|
-
#
|
110
|
-
# ["a", "b", "c"]
|
111
|
-
#
|
112
|
-
def parse_array(name)
|
113
|
-
return shift if peek.is_a?(Array)
|
114
|
-
array = []
|
115
|
-
|
116
|
-
while current_is_value?
|
117
|
-
array << shift
|
118
|
-
end
|
119
|
-
array
|
120
|
-
end
|
121
|
-
|
122
|
-
# Check if the peek is numeric format and return a Float or Integer.
|
123
|
-
# Otherwise raises an error.
|
124
|
-
#
|
125
|
-
def parse_numeric(name)
|
126
|
-
return shift if peek.is_a?(Numeric)
|
127
|
-
|
128
|
-
unless peek =~ NUMERIC && $& == peek
|
129
|
-
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
|
130
|
-
end
|
131
|
-
|
132
|
-
$&.index('.') ? shift.to_f : shift.to_i
|
133
|
-
end
|
134
|
-
|
135
|
-
# Parse string:
|
136
|
-
# for --string-arg, just return the current value in the pile
|
137
|
-
# for --no-string-arg, nil
|
138
|
-
#
|
139
|
-
def parse_string(name)
|
140
|
-
if no_or_skip?(name)
|
141
|
-
nil
|
142
|
-
else
|
143
|
-
shift
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# Raises an error if @non_assigned_required array is not empty.
|
148
|
-
#
|
149
|
-
def check_requirement!
|
150
|
-
unless @non_assigned_required.empty?
|
151
|
-
names = @non_assigned_required.map do |o|
|
152
|
-
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
|
153
|
-
end.join("', '")
|
154
|
-
|
155
|
-
class_name = self.class.name.split('::').last.downcase
|
156
|
-
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
end
|
@@ -1,128 +0,0 @@
|
|
1
|
-
class Thor
|
2
|
-
class Option < Argument #:nodoc:
|
3
|
-
attr_reader :aliases, :group
|
4
|
-
|
5
|
-
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
6
|
-
|
7
|
-
def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil)
|
8
|
-
super(name, description, required, type, default, banner)
|
9
|
-
@aliases = [*aliases].compact
|
10
|
-
@group = group.to_s.capitalize if group
|
11
|
-
end
|
12
|
-
|
13
|
-
# This parse quick options given as method_options. It makes several
|
14
|
-
# assumptions, but you can be more specific using the option method.
|
15
|
-
#
|
16
|
-
# parse :foo => "bar"
|
17
|
-
# #=> Option foo with default value bar
|
18
|
-
#
|
19
|
-
# parse [:foo, :baz] => "bar"
|
20
|
-
# #=> Option foo with default value bar and alias :baz
|
21
|
-
#
|
22
|
-
# parse :foo => :required
|
23
|
-
# #=> Required option foo without default value
|
24
|
-
#
|
25
|
-
# parse :foo => 2
|
26
|
-
# #=> Option foo with default value 2 and type numeric
|
27
|
-
#
|
28
|
-
# parse :foo => :numeric
|
29
|
-
# #=> Option foo without default value and type numeric
|
30
|
-
#
|
31
|
-
# parse :foo => true
|
32
|
-
# #=> Option foo with default value true and type boolean
|
33
|
-
#
|
34
|
-
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
|
35
|
-
# is given a default type is assumed. This default type accepts arguments as
|
36
|
-
# string (--foo=value) or booleans (just --foo).
|
37
|
-
#
|
38
|
-
# By default all options are optional, unless :required is given.
|
39
|
-
#
|
40
|
-
def self.parse(key, value)
|
41
|
-
if key.is_a?(Array)
|
42
|
-
name, *aliases = key
|
43
|
-
else
|
44
|
-
name, aliases = key, []
|
45
|
-
end
|
46
|
-
|
47
|
-
name = name.to_s
|
48
|
-
default = value
|
49
|
-
|
50
|
-
type = case value
|
51
|
-
when Symbol
|
52
|
-
default = nil
|
53
|
-
|
54
|
-
if VALID_TYPES.include?(value)
|
55
|
-
value
|
56
|
-
elsif required = (value == :required)
|
57
|
-
:string
|
58
|
-
end
|
59
|
-
when TrueClass, FalseClass
|
60
|
-
:boolean
|
61
|
-
when Numeric
|
62
|
-
:numeric
|
63
|
-
when Hash, Array, String
|
64
|
-
value.class.name.downcase.to_sym
|
65
|
-
end
|
66
|
-
|
67
|
-
self.new(name.to_s, nil, required, type, default, nil, nil, aliases)
|
68
|
-
end
|
69
|
-
|
70
|
-
def switch_name
|
71
|
-
@switch_name ||= dasherized? ? name : dasherize(name)
|
72
|
-
end
|
73
|
-
|
74
|
-
def human_name
|
75
|
-
@human_name ||= dasherized? ? undasherize(name) : name
|
76
|
-
end
|
77
|
-
|
78
|
-
def usage(padding=0)
|
79
|
-
sample = if banner && !banner.to_s.empty?
|
80
|
-
"#{switch_name}=#{banner}"
|
81
|
-
else
|
82
|
-
switch_name
|
83
|
-
end
|
84
|
-
|
85
|
-
sample = "[#{sample}]" unless required?
|
86
|
-
|
87
|
-
if aliases.empty?
|
88
|
-
(" " * padding) << sample
|
89
|
-
else
|
90
|
-
"#{aliases.join(', ')}, #{sample}"
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Allow some type predicates as: boolean?, string? and etc.
|
95
|
-
#
|
96
|
-
def method_missing(method, *args, &block)
|
97
|
-
given = method.to_s.sub(/\?$/, '').to_sym
|
98
|
-
if valid_type?(given)
|
99
|
-
self.type == given
|
100
|
-
else
|
101
|
-
super
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
protected
|
106
|
-
|
107
|
-
def validate!
|
108
|
-
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
109
|
-
end
|
110
|
-
|
111
|
-
def valid_type?(type)
|
112
|
-
VALID_TYPES.include?(type.to_sym)
|
113
|
-
end
|
114
|
-
|
115
|
-
def dasherized?
|
116
|
-
name.index('-') == 0
|
117
|
-
end
|
118
|
-
|
119
|
-
def undasherize(str)
|
120
|
-
str.sub(/^-{1,2}/, '')
|
121
|
-
end
|
122
|
-
|
123
|
-
def dasherize(str)
|
124
|
-
(str.length > 1 ? "--" : "-") + str.gsub('_', '-')
|
125
|
-
end
|
126
|
-
|
127
|
-
end
|
128
|
-
end
|