ruby_fly 0.38.0.pre.2 → 0.38.0.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +37 -2
- data/Rakefile +83 -46
- data/bin/console +4 -3
- data/lib/ruby_fly/commands/base.rb +11 -16
- data/lib/ruby_fly/commands/destroy_pipeline.rb +35 -23
- data/lib/ruby_fly/commands/get_pipeline.rb +21 -19
- data/lib/ruby_fly/commands/login.rb +44 -24
- data/lib/ruby_fly/commands/mixins/environment.rb +7 -5
- data/lib/ruby_fly/commands/mixins/required_params.rb +35 -0
- data/lib/ruby_fly/commands/set_pipeline.rb +62 -34
- data/lib/ruby_fly/commands/status.rb +22 -24
- data/lib/ruby_fly/commands/unpause_pipeline.rb +28 -21
- data/lib/ruby_fly/commands/version.rb +5 -3
- data/lib/ruby_fly/commands.rb +3 -1
- data/lib/ruby_fly/rc.rb +69 -83
- data/lib/ruby_fly/version.rb +3 -1
- data/lib/ruby_fly.rb +4 -0
- data/ruby_fly.gemspec +57 -0
- metadata +67 -8
@@ -1,52 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'lino'
|
2
4
|
require_relative 'base'
|
3
5
|
require_relative 'mixins/environment'
|
6
|
+
require_relative 'mixins/required_params'
|
4
7
|
|
5
8
|
module RubyFly
|
6
9
|
module Commands
|
7
10
|
class SetPipeline < Base
|
8
11
|
include Mixins::Environment
|
12
|
+
include Mixins::RequiredParams
|
9
13
|
|
14
|
+
# rubocop:disable Metrics/AbcSize
|
15
|
+
# rubocop:disable Metrics/MethodLength
|
10
16
|
def configure_command(builder, opts)
|
11
17
|
builder = super(builder, opts)
|
12
|
-
|
13
|
-
missing_params = [
|
14
|
-
:target,
|
15
|
-
:pipeline,
|
16
|
-
:config
|
17
|
-
].select { |param| opts[param].nil? }
|
18
|
-
|
19
|
-
unless missing_params.empty?
|
20
|
-
description = missing_params.map { |p| "'#{p}'" }.join(', ')
|
21
|
-
raise(
|
22
|
-
ArgumentError,
|
23
|
-
"Error: #{description} required but not provided.")
|
24
|
-
end
|
25
|
-
|
26
|
-
target = opts[:target]
|
27
|
-
pipeline = opts[:pipeline]
|
28
|
-
config = opts[:config]
|
29
|
-
vars = opts[:vars] || {}
|
30
|
-
var_files = opts[:var_files] || []
|
31
|
-
non_interactive = opts[:non_interactive]
|
32
|
-
team = opts[:team]
|
33
|
-
|
34
18
|
builder
|
35
19
|
.with_subcommand('set-pipeline') do |sub|
|
36
|
-
sub = sub
|
37
|
-
sub = sub
|
38
|
-
sub = sub
|
39
|
-
sub = sub
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
var_files.each do |var_file|
|
44
|
-
sub = sub.with_option('-l', var_file)
|
45
|
-
end
|
46
|
-
sub = sub.with_flag('-n') if non_interactive
|
20
|
+
sub = with_target(sub, opts[:target])
|
21
|
+
sub = with_pipeline(sub, opts[:pipeline])
|
22
|
+
sub = with_config(sub, opts[:config])
|
23
|
+
sub = with_team(sub, opts[:team])
|
24
|
+
sub = with_vars(sub, opts[:vars])
|
25
|
+
sub = with_var_files(sub, opts[:var_files])
|
26
|
+
sub = with_non_interactive(sub, opts[:non_interactive])
|
47
27
|
sub
|
48
28
|
end
|
49
29
|
end
|
30
|
+
# rubocop:enable Metrics/MethodLength
|
31
|
+
# rubocop:enable Metrics/AbcSize
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def required_params
|
36
|
+
%i[target pipeline config]
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_target(sub, target)
|
40
|
+
sub.with_option('-t', target)
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_pipeline(sub, pipeline)
|
44
|
+
sub.with_option('-p', pipeline)
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_config(sub, config)
|
48
|
+
sub.with_option('-c', config)
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_team(sub, team)
|
52
|
+
return sub unless team
|
53
|
+
|
54
|
+
sub.with_option('--team', team)
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_vars(builder, vars)
|
58
|
+
vars ||= {}
|
59
|
+
vars.each do |key, value|
|
60
|
+
builder = builder.with_option('-v', "'#{key}=#{value}'")
|
61
|
+
end
|
62
|
+
builder
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_var_files(builder, var_files)
|
66
|
+
var_files ||= []
|
67
|
+
var_files.each do |var_file|
|
68
|
+
builder = builder.with_option('-l', var_file)
|
69
|
+
end
|
70
|
+
builder
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_non_interactive(builder, non_interactive)
|
74
|
+
return builder unless non_interactive
|
75
|
+
|
76
|
+
builder.with_flag('-n')
|
77
|
+
end
|
50
78
|
end
|
51
79
|
end
|
52
|
-
end
|
80
|
+
end
|
@@ -1,52 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'lino'
|
2
4
|
require_relative 'base'
|
3
5
|
require_relative 'mixins/environment'
|
6
|
+
require_relative 'mixins/required_params'
|
4
7
|
|
5
8
|
module RubyFly
|
6
9
|
module Commands
|
7
10
|
class Status < Base
|
8
11
|
include Mixins::Environment
|
12
|
+
include Mixins::RequiredParams
|
9
13
|
|
10
14
|
def initialize(*args)
|
11
15
|
super(*args)
|
12
16
|
@stdout = StringIO.new unless
|
13
|
-
|
17
|
+
defined?(@stdout) && @stdout.respond_to?(:string)
|
14
18
|
@stderr = StringIO.new unless
|
15
|
-
|
19
|
+
defined?(@stderr) && @stderr.respond_to?(:string)
|
16
20
|
end
|
17
21
|
|
18
22
|
def configure_command(builder, opts)
|
19
23
|
builder = super(builder, opts)
|
20
|
-
|
21
|
-
missing_params = [
|
22
|
-
:target
|
23
|
-
].select { |param| opts[param].nil? }
|
24
|
-
|
25
|
-
unless missing_params.empty?
|
26
|
-
description = missing_params.map { |p| "'#{p}'" }.join(', ')
|
27
|
-
raise(
|
28
|
-
ArgumentError,
|
29
|
-
"Error: #{description} required but not provided.")
|
30
|
-
end
|
31
|
-
|
32
|
-
target = opts[:target]
|
33
|
-
|
34
24
|
builder
|
35
25
|
.with_subcommand('status') do |sub|
|
36
|
-
sub = sub
|
26
|
+
sub = with_target(sub, opts[:target])
|
37
27
|
sub
|
38
28
|
end
|
39
29
|
end
|
40
30
|
|
41
31
|
def do_around(opts, &block)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
raise e unless e.status.exitstatus == 1
|
46
|
-
end
|
32
|
+
block.call(opts)
|
33
|
+
rescue Open4::SpawnError => e
|
34
|
+
raise e unless e.status.exitstatus == 1
|
47
35
|
end
|
48
36
|
|
49
|
-
def do_after(
|
37
|
+
def do_after(_opts)
|
50
38
|
output = stdout.string
|
51
39
|
error = stderr.string
|
52
40
|
|
@@ -57,6 +45,16 @@ module RubyFly
|
|
57
45
|
|
58
46
|
:unknown_status
|
59
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def required_params
|
52
|
+
%i[target]
|
53
|
+
end
|
54
|
+
|
55
|
+
def with_target(sub, target)
|
56
|
+
sub.with_option('-t', target)
|
57
|
+
end
|
60
58
|
end
|
61
59
|
end
|
62
|
-
end
|
60
|
+
end
|
@@ -1,39 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'lino'
|
2
4
|
require_relative 'base'
|
3
5
|
require_relative 'mixins/environment'
|
6
|
+
require_relative 'mixins/required_params'
|
4
7
|
|
5
8
|
module RubyFly
|
6
9
|
module Commands
|
7
10
|
class UnpausePipeline < Base
|
8
11
|
include Mixins::Environment
|
12
|
+
include Mixins::RequiredParams
|
9
13
|
|
10
14
|
def configure_command(builder, opts)
|
11
15
|
builder = super(builder, opts)
|
16
|
+
builder
|
17
|
+
.with_subcommand('unpause-pipeline') do |sub|
|
18
|
+
sub = with_target(sub, opts[:target])
|
19
|
+
sub = with_pipeline(sub, opts[:pipeline])
|
20
|
+
sub = with_team(sub, opts[:team])
|
21
|
+
sub
|
22
|
+
end
|
23
|
+
end
|
12
24
|
|
13
|
-
|
14
|
-
:target,
|
15
|
-
:pipeline
|
16
|
-
].select { |param| opts[param].nil? }
|
25
|
+
private
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
ArgumentError,
|
22
|
-
"Error: #{description} required but not provided.")
|
23
|
-
end
|
27
|
+
def required_params
|
28
|
+
%i[target pipeline]
|
29
|
+
end
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
def with_target(sub, target)
|
32
|
+
sub.with_option('-t', target)
|
33
|
+
end
|
28
34
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
def with_pipeline(sub, pipeline)
|
36
|
+
sub.with_option('-p', pipeline)
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_team(sub, team)
|
40
|
+
return sub unless team
|
41
|
+
|
42
|
+
sub.with_option('--team', team)
|
36
43
|
end
|
37
44
|
end
|
38
45
|
end
|
39
|
-
end
|
46
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'lino'
|
2
4
|
require_relative 'base'
|
3
5
|
|
@@ -8,15 +10,15 @@ module RubyFly
|
|
8
10
|
@version_string
|
9
11
|
end
|
10
12
|
|
11
|
-
def do_before(
|
13
|
+
def do_before(_opts)
|
12
14
|
@version_string = StringIO.new
|
13
15
|
end
|
14
16
|
|
15
|
-
def configure_command(builder,
|
17
|
+
def configure_command(builder, _opts)
|
16
18
|
builder.with_flag('--version')
|
17
19
|
end
|
18
20
|
|
19
|
-
def do_after(
|
21
|
+
def do_after(_opts)
|
20
22
|
@version_string.string.gsub(/\n/, '')
|
21
23
|
end
|
22
24
|
end
|
data/lib/ruby_fly/commands.rb
CHANGED
data/lib/ruby_fly/rc.rb
CHANGED
@@ -1,44 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
4
|
|
3
5
|
module RubyFly
|
4
6
|
class RC
|
5
7
|
module Conversions
|
6
8
|
def self.symbolize_keys(hash)
|
7
|
-
hash.
|
8
|
-
new_key =
|
9
|
-
|
10
|
-
key.to_sym
|
11
|
-
else
|
12
|
-
key
|
13
|
-
end
|
14
|
-
new_value = case value
|
15
|
-
when Hash then
|
16
|
-
symbolize_keys(value)
|
17
|
-
else
|
18
|
-
value
|
19
|
-
end
|
9
|
+
hash.each_with_object({}) do |(key, value), result|
|
10
|
+
new_key = key.is_a?(String) ? key.to_sym : key
|
11
|
+
new_value = value.is_a?(Hash) ? symbolize_keys(value) : value
|
20
12
|
result[new_key] = new_value
|
21
|
-
|
22
|
-
}
|
13
|
+
end
|
23
14
|
end
|
24
15
|
|
25
16
|
def self.stringify_keys(hash)
|
26
|
-
hash.
|
27
|
-
new_key =
|
28
|
-
|
29
|
-
key.to_s
|
30
|
-
else
|
31
|
-
key
|
32
|
-
end
|
33
|
-
new_value = case value
|
34
|
-
when Hash then
|
35
|
-
stringify_keys(value)
|
36
|
-
else
|
37
|
-
value
|
38
|
-
end
|
17
|
+
hash.each_with_object({}) do |(key, value), result|
|
18
|
+
new_key = key.is_a?(Symbol) ? key.to_s : key
|
19
|
+
new_value = value.is_a?(Hash) ? stringify_keys(value) : value
|
39
20
|
result[new_key] = new_value
|
40
|
-
|
41
|
-
}
|
21
|
+
end
|
42
22
|
end
|
43
23
|
end
|
44
24
|
|
@@ -55,18 +35,18 @@ module RubyFly
|
|
55
35
|
end
|
56
36
|
|
57
37
|
class Target
|
58
|
-
|
59
|
-
|
38
|
+
attr_accessor :api, :team, :token
|
39
|
+
attr_reader :name
|
60
40
|
|
61
41
|
def self.clone(target, overrides = {})
|
62
|
-
if target.nil?
|
63
|
-
|
64
|
-
end
|
42
|
+
return target if target.nil?
|
43
|
+
|
65
44
|
Target.new(
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
45
|
+
name: overrides[:name] || target.name,
|
46
|
+
api: overrides[:api] || target.api,
|
47
|
+
team: overrides[:team] || target.team,
|
48
|
+
token: overrides[:token] || target.token
|
49
|
+
)
|
70
50
|
end
|
71
51
|
|
72
52
|
def initialize(parameters)
|
@@ -78,25 +58,22 @@ module RubyFly
|
|
78
58
|
|
79
59
|
def bearer_token=(value)
|
80
60
|
@token = {
|
81
|
-
|
82
|
-
|
61
|
+
type: 'bearer',
|
62
|
+
value: value
|
83
63
|
}
|
84
64
|
end
|
85
65
|
|
86
66
|
def bearer_token
|
87
|
-
@token[:type] == 'bearer'
|
88
|
-
@token[:value] :
|
89
|
-
nil
|
67
|
+
@token[:value] if @token[:type] == 'bearer'
|
90
68
|
end
|
91
69
|
|
92
70
|
def encode_with(coder)
|
93
71
|
coder.represent_map(
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
}))
|
72
|
+
nil,
|
73
|
+
RubyFly::RC::Conversions.stringify_keys(
|
74
|
+
{ api: @api, team: @team.to_s, token: @token }
|
75
|
+
)
|
76
|
+
)
|
100
77
|
end
|
101
78
|
|
102
79
|
def ==(other)
|
@@ -123,18 +100,30 @@ module RubyFly
|
|
123
100
|
name = options[:name] || '.flyrc'
|
124
101
|
path = File.join(home, name)
|
125
102
|
|
126
|
-
contents = options[:contents] ||
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
103
|
+
contents = options[:contents] || try_load_rc_file_contents(path) || {}
|
104
|
+
targets = try_load_rc_file_targets(path, contents) || []
|
105
|
+
|
106
|
+
RubyFly::RC.new(home: home, name: name, targets: targets)
|
107
|
+
end
|
108
|
+
|
109
|
+
class << self
|
110
|
+
private
|
111
|
+
|
112
|
+
def rc_file_exists?(path)
|
113
|
+
File.exist?(path)
|
114
|
+
end
|
115
|
+
|
116
|
+
def try_load_rc_file_contents(path)
|
117
|
+
return unless rc_file_exists?(path)
|
118
|
+
|
119
|
+
RubyFly::RC::Conversions.symbolize_keys(YAML.load_file(path))
|
120
|
+
end
|
121
|
+
|
122
|
+
def try_load_rc_file_targets(path, contents)
|
123
|
+
return unless rc_file_exists?(path)
|
133
124
|
|
134
|
-
|
135
|
-
|
136
|
-
name: name,
|
137
|
-
targets: targets)
|
125
|
+
contents[:targets].map { |n, t| Target.new(t.merge(name: n)) }
|
126
|
+
end
|
138
127
|
end
|
139
128
|
|
140
129
|
def initialize(options)
|
@@ -149,25 +138,23 @@ module RubyFly
|
|
149
138
|
@targets.values
|
150
139
|
end
|
151
140
|
|
152
|
-
def
|
153
|
-
|
141
|
+
def target?(target_name)
|
142
|
+
@targets.key?(target_name)
|
154
143
|
end
|
155
144
|
|
156
|
-
def
|
157
|
-
@targets.
|
145
|
+
def find_target(target_name)
|
146
|
+
Target.clone(@targets[target_name.to_sym])
|
158
147
|
end
|
159
148
|
|
160
149
|
def add_target(target)
|
161
|
-
if
|
162
|
-
|
163
|
-
end
|
150
|
+
raise TargetAlreadyPresentError, target.name if target?(target.name)
|
151
|
+
|
164
152
|
@targets[target.name] = target
|
165
153
|
end
|
166
154
|
|
167
155
|
def update_target(target_name, &block)
|
168
|
-
unless
|
169
|
-
|
170
|
-
end
|
156
|
+
raise TargetNotPresentError, target_name unless target?(target_name)
|
157
|
+
|
171
158
|
mutable_target = find_target(target_name)
|
172
159
|
block.call(mutable_target)
|
173
160
|
updated_target = Target.clone(mutable_target, name: target_name)
|
@@ -175,9 +162,11 @@ module RubyFly
|
|
175
162
|
end
|
176
163
|
|
177
164
|
def add_or_update_target(target_name, &block)
|
178
|
-
mutable_target =
|
179
|
-
|
180
|
-
|
165
|
+
mutable_target = if target?(target_name)
|
166
|
+
find_target(target_name)
|
167
|
+
else
|
168
|
+
Target.new({ name: target_name })
|
169
|
+
end
|
181
170
|
block.call(mutable_target)
|
182
171
|
updated_target = Target.clone(mutable_target, name: target_name)
|
183
172
|
@targets[target_name] = updated_target
|
@@ -191,23 +180,20 @@ module RubyFly
|
|
191
180
|
end
|
192
181
|
|
193
182
|
def remove_target(target_name)
|
194
|
-
unless
|
195
|
-
|
196
|
-
end
|
183
|
+
raise TargetNotPresentError, target_name unless target?(target_name)
|
184
|
+
|
197
185
|
@targets.delete(target_name)
|
198
186
|
end
|
199
187
|
|
200
188
|
def to_yaml
|
201
189
|
RubyFly::RC::Conversions
|
202
|
-
|
203
|
-
|
190
|
+
.stringify_keys({ targets: @targets })
|
191
|
+
.to_yaml
|
204
192
|
end
|
205
193
|
|
206
194
|
def write!
|
207
195
|
FileUtils.mkdir_p(@home)
|
208
|
-
File.
|
209
|
-
file.write(to_yaml)
|
210
|
-
end
|
196
|
+
File.write(File.join(@home, @name), to_yaml)
|
211
197
|
end
|
212
198
|
end
|
213
|
-
end
|
199
|
+
end
|
data/lib/ruby_fly/version.rb
CHANGED
data/lib/ruby_fly.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ruby_fly/version'
|
2
4
|
require 'ruby_fly/commands'
|
3
5
|
require 'ruby_fly/rc'
|
@@ -32,9 +34,11 @@ module RubyFly
|
|
32
34
|
Commands::GetPipeline.new.execute(opts)
|
33
35
|
end
|
34
36
|
|
37
|
+
# rubocop:disable Naming/AccessorMethodName
|
35
38
|
def set_pipeline(opts = {})
|
36
39
|
Commands::SetPipeline.new.execute(opts)
|
37
40
|
end
|
41
|
+
# rubocop:enable Naming/AccessorMethodName
|
38
42
|
|
39
43
|
def unpause_pipeline(opts = {})
|
40
44
|
Commands::UnpausePipeline.new.execute(opts)
|
data/ruby_fly.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'ruby_fly/version'
|
6
|
+
|
7
|
+
files = %w[
|
8
|
+
bin
|
9
|
+
lib
|
10
|
+
CODE_OF_CONDUCT.md
|
11
|
+
ruby_fly.gemspec
|
12
|
+
Gemfile
|
13
|
+
LICENSE.txt
|
14
|
+
Rakefile
|
15
|
+
README.md
|
16
|
+
]
|
17
|
+
|
18
|
+
Gem::Specification.new do |spec|
|
19
|
+
spec.name = 'ruby_fly'
|
20
|
+
spec.version = RubyFly::VERSION
|
21
|
+
spec.authors = ['InfraBlocks Maintainers']
|
22
|
+
spec.email = ['maintainers@infrablocks.io']
|
23
|
+
|
24
|
+
spec.summary = 'A simple Ruby wrapper for invoking fly commands.'
|
25
|
+
spec.description = 'Wraps the concourse fly CLI so that fly can be invoked '\
|
26
|
+
'from a Ruby script or Rakefile.'
|
27
|
+
spec.homepage = 'https://github.com/infrablocks/ruby_fly'
|
28
|
+
spec.license = 'MIT'
|
29
|
+
|
30
|
+
spec.files = `git ls-files -z`.split("\x0").select do |f|
|
31
|
+
f.match(/^(#{files.map { |g| Regexp.escape(g) }.join('|')})/)
|
32
|
+
end
|
33
|
+
spec.bindir = 'exe'
|
34
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
35
|
+
spec.require_paths = ['lib']
|
36
|
+
|
37
|
+
spec.required_ruby_version = '>= 2.7'
|
38
|
+
|
39
|
+
spec.add_dependency 'lino', '~> 3.0'
|
40
|
+
|
41
|
+
spec.add_development_dependency 'bundler'
|
42
|
+
spec.add_development_dependency 'fakefs'
|
43
|
+
spec.add_development_dependency 'faker'
|
44
|
+
spec.add_development_dependency 'gem-release'
|
45
|
+
spec.add_development_dependency 'rake'
|
46
|
+
spec.add_development_dependency 'rake_circle_ci'
|
47
|
+
spec.add_development_dependency 'rake_github'
|
48
|
+
spec.add_development_dependency 'rake_gpg'
|
49
|
+
spec.add_development_dependency 'rake_ssh'
|
50
|
+
spec.add_development_dependency 'rspec'
|
51
|
+
spec.add_development_dependency 'rubocop'
|
52
|
+
spec.add_development_dependency 'rubocop-rake'
|
53
|
+
spec.add_development_dependency 'rubocop-rspec'
|
54
|
+
spec.add_development_dependency 'simplecov'
|
55
|
+
|
56
|
+
spec.metadata['rubygems_mfa_required'] = 'false'
|
57
|
+
end
|