commander-openflighthpc 1.0.0.pre.alpha1
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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +44 -0
- data/.rubocop_todo.yml +77 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/LICENSE +51 -0
- data/Manifest +38 -0
- data/README.md +492 -0
- data/Rakefile +13 -0
- data/bin/commander +104 -0
- data/commander-openflighthpc.gemspec +32 -0
- data/lib/commander.rb +36 -0
- data/lib/commander/blank.rb +7 -0
- data/lib/commander/command.rb +263 -0
- data/lib/commander/configure.rb +14 -0
- data/lib/commander/core_ext.rb +2 -0
- data/lib/commander/core_ext/array.rb +24 -0
- data/lib/commander/core_ext/object.rb +8 -0
- data/lib/commander/delegates.rb +27 -0
- data/lib/commander/help_formatters.rb +52 -0
- data/lib/commander/help_formatters/base.rb +24 -0
- data/lib/commander/help_formatters/terminal.rb +24 -0
- data/lib/commander/help_formatters/terminal/command_help.erb +35 -0
- data/lib/commander/help_formatters/terminal/help.erb +36 -0
- data/lib/commander/help_formatters/terminal/subcommand_help.erb +23 -0
- data/lib/commander/help_formatters/terminal_compact.rb +11 -0
- data/lib/commander/help_formatters/terminal_compact/command_help.erb +26 -0
- data/lib/commander/help_formatters/terminal_compact/help.erb +29 -0
- data/lib/commander/help_formatters/terminal_compact/subcommand_help.erb +15 -0
- data/lib/commander/import.rb +5 -0
- data/lib/commander/methods.rb +11 -0
- data/lib/commander/patches/decimal-integer.rb +17 -0
- data/lib/commander/patches/help_formatter_binding.rb +15 -0
- data/lib/commander/patches/implicit-short-tags.rb +75 -0
- data/lib/commander/patches/option_defaults.rb +23 -0
- data/lib/commander/patches/validate_inputs.rb +76 -0
- data/lib/commander/platform.rb +7 -0
- data/lib/commander/runner.rb +493 -0
- data/lib/commander/user_interaction.rb +551 -0
- data/lib/commander/version.rb +3 -0
- data/spec/command_spec.rb +157 -0
- data/spec/configure_spec.rb +37 -0
- data/spec/core_ext/array_spec.rb +18 -0
- data/spec/core_ext/object_spec.rb +19 -0
- data/spec/help_formatters/terminal_compact_spec.rb +69 -0
- data/spec/help_formatters/terminal_spec.rb +67 -0
- data/spec/methods_spec.rb +61 -0
- data/spec/patches/validate_inputs_spec.rb +84 -0
- data/spec/runner_spec.rb +672 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/ui_spec.rb +30 -0
- metadata +183 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commander::Command do
|
4
|
+
include Commander::Methods
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
mock_terminal
|
8
|
+
create_test_command
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Options' do
|
12
|
+
before :each do
|
13
|
+
@options = Commander::Command::Options.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should act like an open struct' do
|
17
|
+
@options.send = 'mail'
|
18
|
+
@options.call = true
|
19
|
+
expect(@options.send).to eq('mail')
|
20
|
+
expect(@options.call).to eq(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should allow __send__ to function as always' do
|
24
|
+
@options.send = 'foo'
|
25
|
+
expect(@options.__send__(:send)).to eq('foo')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#option' do
|
30
|
+
it 'should add options' do
|
31
|
+
expect { @command.option '--recursive' }.to change(@command.options, :length).from(1).to(2)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should allow procs as option handlers' do
|
35
|
+
@command.option('--recursive') { |recursive| expect(recursive).to be true }
|
36
|
+
@command.run '--recursive'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should allow usage of common method names' do
|
40
|
+
@command.option '--open file'
|
41
|
+
@command.when_called { |_, options| expect(options.open).to eq('foo') }
|
42
|
+
@command.run '--open', 'foo'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#run' do
|
47
|
+
describe 'should invoke #when_called' do
|
48
|
+
it 'with arguments seperated from options' do
|
49
|
+
@command.when_called { |args, _options| expect(args.join(' ')).to eq('just some args') }
|
50
|
+
@command.run '--verbose', 'just', 'some', 'args'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'calling the #call method by default when an object is called' do
|
54
|
+
object = double 'Object'
|
55
|
+
expect(object).to receive(:call).once
|
56
|
+
@command.when_called object
|
57
|
+
@command.run 'foo'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should allow #action as an alias to #when_called' do
|
61
|
+
object = double 'Object'
|
62
|
+
expect(object).to receive(:call).once
|
63
|
+
@command.action object
|
64
|
+
@command.run 'foo'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'calling an arbitrary method when an object is called' do
|
68
|
+
object = double 'Object'
|
69
|
+
expect(object).to receive(:foo).once
|
70
|
+
@command.when_called object, :foo
|
71
|
+
@command.run 'foo'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should raise an error when no handler is present' do
|
75
|
+
expect { @command.when_called }.to raise_error(ArgumentError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'should populate options with' do
|
80
|
+
it 'boolean values' do
|
81
|
+
@command.option '--[no-]toggle'
|
82
|
+
@command.when_called { |_, options| expect(options.toggle).to be true }
|
83
|
+
@command.run '--toggle'
|
84
|
+
@command.when_called { |_, options| expect(options.toggle).to be false }
|
85
|
+
@command.run '--no-toggle'
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'mandatory arguments' do
|
89
|
+
@command.option '--file FILE'
|
90
|
+
@command.when_called { |_, options| expect(options.file).to eq('foo') }
|
91
|
+
@command.run '--file', 'foo'
|
92
|
+
expect { @command.run '--file' }.to raise_error(OptionParser::MissingArgument)
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'optional arguments' do
|
96
|
+
before do
|
97
|
+
@command.option '--use-config [file] '
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should return the argument when provided' do
|
101
|
+
@command.when_called { |_, options| expect(options.use_config).to eq('foo') }
|
102
|
+
@command.run '--use-config', 'foo'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return true when present without an argument' do
|
106
|
+
@command.when_called { |_, options| expect(options.use_config).to be true }
|
107
|
+
@command.run '--use-config'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should return nil when not present' do
|
111
|
+
@command.when_called { |_, options| expect(options.use_config).to be_nil }
|
112
|
+
@command.run
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'typed arguments' do
|
117
|
+
before do
|
118
|
+
@command.option '--interval N', Integer
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should parse valid values' do
|
122
|
+
@command.when_called { |_, options| expect(options.interval).to eq(5) }
|
123
|
+
@command.run '--interval', '5'
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should reject invalid values' do
|
127
|
+
expect { @command.run '--interval', 'invalid' }.to raise_error(OptionParser::InvalidArgument)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'lists' do
|
132
|
+
@command.option '--fav COLORS', Array
|
133
|
+
@command.when_called { |_, options| expect(options.fav).to eq(%w(red green blue)) }
|
134
|
+
@command.run '--fav', 'red,green,blue'
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'lists with multi-word items' do
|
138
|
+
@command.option '--fav MOVIES', Array
|
139
|
+
@command.when_called { |_, options| expect(options.fav).to eq(['super\ bad', 'nightmare']) }
|
140
|
+
@command.run '--fav', 'super\ bad,nightmare'
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'defaults' do
|
144
|
+
@command.option '--files LIST', Array
|
145
|
+
@command.option '--interval N', Integer
|
146
|
+
@command.when_called do |_, options|
|
147
|
+
options.default \
|
148
|
+
files: %w(foo bar),
|
149
|
+
interval: 5
|
150
|
+
expect(options.files).to eq(%w(foo bar))
|
151
|
+
expect(options.interval).to eq(15)
|
152
|
+
end
|
153
|
+
@command.run '--interval', '15'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'commander/configure'
|
3
|
+
|
4
|
+
describe Commander do
|
5
|
+
describe '.configure' do
|
6
|
+
it 'calls the given block' do
|
7
|
+
expect { Commander.configure { throw :block_called } }.to throw_symbol(:block_called)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'called block' do
|
11
|
+
before(:each) do
|
12
|
+
allow(Commander::Runner.instance).to receive(:run!)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'provides Commander configuration methods' do
|
16
|
+
Commander.configure do
|
17
|
+
program :name, 'test'
|
18
|
+
end
|
19
|
+
|
20
|
+
expect(Commander::Runner.instance.program(:name)).to eq('test')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'passes all arguments to the block' do
|
24
|
+
Commander.configure('foo') do |first_arg|
|
25
|
+
program :name, first_arg
|
26
|
+
end
|
27
|
+
|
28
|
+
expect(Commander::Runner.instance.program(:name)).to eq('foo')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'calls Runner#run! after calling the configuration block' do
|
33
|
+
expect(Commander::Runner.instance).to receive(:run!)
|
34
|
+
Commander.configure {}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
describe '#parse' do
|
5
|
+
it 'should seperate a list of words into an array' do
|
6
|
+
expect(Array.parse('just a test')).to eq(%w(just a test))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should preserve escaped whitespace' do
|
10
|
+
expect(Array.parse('just a\ test')).to eq(['just', 'a test'])
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should match %w behavior with multiple backslashes' do
|
14
|
+
str = 'just a\\ test'
|
15
|
+
expect(Array.parse(str)).to eq(eval("%w(#{str})"))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Object do
|
4
|
+
describe '#get_binding' do
|
5
|
+
it 'should return the objects binding' do
|
6
|
+
expect(-> {}.get_binding).to be_instance_of(Binding)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#method_missing' do
|
11
|
+
it 'should preserve its original behavior for missing methods' do
|
12
|
+
expect { send(:i_am_a_missing_method) }.to raise_error(NoMethodError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should preserve its original behavior for missing variables' do
|
16
|
+
expect { i_am_a_missing_variable }.to raise_error(NameError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commander::HelpFormatter::TerminalCompact do
|
4
|
+
include Commander::Methods
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
mock_terminal
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'global help' do
|
11
|
+
before :each do
|
12
|
+
new_command_runner 'help' do
|
13
|
+
program :help_formatter, :compact
|
14
|
+
command :'install gem' do |c|
|
15
|
+
c.syntax = 'foo install gem [options]'
|
16
|
+
c.summary = 'Install some gem'
|
17
|
+
end
|
18
|
+
end.run!
|
19
|
+
@global_help = @output.string
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'should display' do
|
23
|
+
it 'the command name' do
|
24
|
+
expect(@global_help).to include('install gem')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'the summary' do
|
28
|
+
expect(@global_help).to include('Install some gem')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'command help' do
|
34
|
+
before :each do
|
35
|
+
new_command_runner 'help', 'install', 'gem' do
|
36
|
+
program :help_formatter, :compact
|
37
|
+
command :'install gem' do |c|
|
38
|
+
c.syntax = 'foo install gem [options]'
|
39
|
+
c.summary = 'Install some gem'
|
40
|
+
c.description = 'Install some gem, blah blah blah'
|
41
|
+
c.example 'one', 'two'
|
42
|
+
c.example 'three', 'four'
|
43
|
+
end
|
44
|
+
end.run!
|
45
|
+
@command_help = @output.string
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'should display' do
|
49
|
+
it 'the command name' do
|
50
|
+
expect(@command_help).to include('install gem')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'the description' do
|
54
|
+
expect(@command_help).to include('Install some gem, blah blah blah')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'all examples' do
|
58
|
+
expect(@command_help).to include('# one')
|
59
|
+
expect(@command_help).to include('two')
|
60
|
+
expect(@command_help).to include('# three')
|
61
|
+
expect(@command_help).to include('four')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'the syntax' do
|
65
|
+
expect(@command_help).to include('foo install gem [options]')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Commander::HelpFormatter::Terminal do
|
4
|
+
include Commander::Methods
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
mock_terminal
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'global help' do
|
11
|
+
before :each do
|
12
|
+
new_command_runner 'help' do
|
13
|
+
command :'install gem' do |c|
|
14
|
+
c.syntax = 'foo install gem [options]'
|
15
|
+
c.summary = 'Install some gem'
|
16
|
+
end
|
17
|
+
end.run!
|
18
|
+
@global_help = @output.string
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'should display' do
|
22
|
+
it 'the command name' do
|
23
|
+
expect(@global_help).to include('install gem')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'the summary' do
|
27
|
+
expect(@global_help).to include('Install some gem')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'command help' do
|
33
|
+
before :each do
|
34
|
+
new_command_runner 'help', 'install', 'gem' do
|
35
|
+
command :'install gem' do |c|
|
36
|
+
c.syntax = 'foo install gem [options]'
|
37
|
+
c.summary = 'Install some gem'
|
38
|
+
c.description = 'Install some gem, blah blah blah'
|
39
|
+
c.example 'one', 'two'
|
40
|
+
c.example 'three', 'four'
|
41
|
+
end
|
42
|
+
end.run!
|
43
|
+
@command_help = @output.string
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'should display' do
|
47
|
+
it 'the command name' do
|
48
|
+
expect(@command_help).to include('install gem')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'the description' do
|
52
|
+
expect(@command_help).to include('Install some gem, blah blah blah')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'all examples' do
|
56
|
+
expect(@command_help).to include('# one')
|
57
|
+
expect(@command_help).to include('two')
|
58
|
+
expect(@command_help).to include('# three')
|
59
|
+
expect(@command_help).to include('four')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'the syntax' do
|
63
|
+
expect(@command_help).to include('foo install gem [options]')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'commander/methods'
|
3
|
+
|
4
|
+
describe Commander::Methods do
|
5
|
+
it 'includes Commander::UI' do
|
6
|
+
expect(subject.ancestors).to include(Commander::UI)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'AskForClass' do
|
10
|
+
it 'includes Commander::UI::AskForClass' do
|
11
|
+
expect(subject.ancestors).to include(Commander::UI::AskForClass)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'defining methods' do
|
15
|
+
let(:terminal) { double }
|
16
|
+
|
17
|
+
before do
|
18
|
+
allow(terminal).to receive(:ask)
|
19
|
+
$_old_terminal = $terminal
|
20
|
+
$terminal = terminal
|
21
|
+
end
|
22
|
+
|
23
|
+
after do
|
24
|
+
$terminal = $_old_terminal
|
25
|
+
end
|
26
|
+
|
27
|
+
subject do
|
28
|
+
Class.new do
|
29
|
+
include Commander::UI::AskForClass
|
30
|
+
end.new
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'defines common "ask_for_*" methods' do
|
34
|
+
expect(subject.respond_to?(:ask_for_float)).to be_truthy
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'responds to "ask_for_*" methods for classes that implement #parse' do
|
38
|
+
expect(subject.respond_to?(:ask_for_datetime)).to be_truthy
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'fails "ask_for_*" method invocations without a prompt' do
|
42
|
+
expect do
|
43
|
+
subject.ask_for_datetime
|
44
|
+
end.to raise_error(ArgumentError)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'implements "ask_for_*"' do
|
48
|
+
expect(terminal).to receive(:ask)
|
49
|
+
subject.ask_for_datetime('hi')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'includes Commander::Delegates' do
|
55
|
+
expect(subject.ancestors).to include(Commander::Delegates)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'does not change the Object ancestors' do
|
59
|
+
expect(Object.ancestors).not_to include(Commander::UI)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commander/patches/validate_inputs'
|
4
|
+
|
5
|
+
# These specs inspired by those in Commander gem in `spec/runner_spec.rb`.
|
6
|
+
|
7
|
+
RSpec.describe Commander::Patches::ValidateInputs do
|
8
|
+
include Commander::Delegates
|
9
|
+
|
10
|
+
def mock_patch_terminal
|
11
|
+
@input = StringIO.new
|
12
|
+
@output = StringIO.new
|
13
|
+
$terminal = HighLine.new @input, @output
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_test_patch_command
|
17
|
+
command :test do |c|
|
18
|
+
c.syntax = 'metal test ARG1 ARG2 [OPTIONAL_ARG3]'
|
19
|
+
c.description = 'test description'
|
20
|
+
c.example 'description', 'command'
|
21
|
+
c.option '-o', '--some-option', 'Some option that does things'
|
22
|
+
c.when_called do |args, _options|
|
23
|
+
format('test %<foo>s', foo: args.join(' '))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@command = command :test
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_multi_word_test_command
|
30
|
+
command :'test do' do |c|
|
31
|
+
c.syntax = 'metal test do ARG1 ARG2'
|
32
|
+
c.when_called do |args, _options|
|
33
|
+
format('test do %<foo>s', foo: args.join(' '))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@command = command :'test do'
|
37
|
+
end
|
38
|
+
|
39
|
+
before do
|
40
|
+
$stderr = StringIO.new
|
41
|
+
mock_patch_terminal
|
42
|
+
create_test_patch_command
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#call' do
|
46
|
+
describe 'validating passed arguments against syntax' do
|
47
|
+
it 'raises if too many arguments given' do
|
48
|
+
expect do
|
49
|
+
command(:test).call(['one', 'two', 'three', 'four'])
|
50
|
+
end.to raise_error(Commander::Patches::CommandUsageError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'raises if too few arguments given' do
|
54
|
+
expect do
|
55
|
+
command(:test).call(['one'])
|
56
|
+
end.to raise_error(Commander::Patches::CommandUsageError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'proceeds as normal if valid number of arguments given' do
|
60
|
+
expect(
|
61
|
+
command(:test).call(['one', 'two', 'three'])
|
62
|
+
).to eql('test one two three')
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'when multi-word command' do
|
66
|
+
before do
|
67
|
+
create_multi_word_test_command
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'raises if too few arguments given' do
|
71
|
+
expect do
|
72
|
+
command(:'test do').call
|
73
|
+
end.to raise_error(Commander::Patches::CommandUsageError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'proceeds as normal if valid number of arguments given' do
|
77
|
+
expect(
|
78
|
+
command(:'test do').call(['one', 'two'])
|
79
|
+
).to eql('test do one two')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|