ci-syntax-tool 0.1.0
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 +3 -0
- data/LICENSE +27 -0
- data/README.md +109 -0
- data/Rakefile +43 -0
- data/bin/ci-syntax-tool +12 -0
- data/ci-syntax-tool.gemspec +27 -0
- data/lib/ci-syntax-tool.rb +6 -0
- data/lib/ci-syntax-tool/checker.rb +63 -0
- data/lib/ci-syntax-tool/command_line.rb +229 -0
- data/lib/ci-syntax-tool/format/base.rb +50 -0
- data/lib/ci-syntax-tool/format/junit.rb +54 -0
- data/lib/ci-syntax-tool/format/progress.rb +60 -0
- data/lib/ci-syntax-tool/format_factory.rb +55 -0
- data/lib/ci-syntax-tool/language/base.rb +56 -0
- data/lib/ci-syntax-tool/language/yaml.rb +41 -0
- data/lib/ci-syntax-tool/language_factory.rb +41 -0
- data/lib/ci-syntax-tool/result.rb +134 -0
- data/lib/ci-syntax-tool/version.rb +10 -0
- data/rubocop.yml +11 -0
- data/test/features/.keep +0 -0
- data/test/features/command-line-help.feature +15 -0
- data/test/features/format-junit.feature +29 -0
- data/test/features/language-yaml.feature +34 -0
- data/test/features/pluggable-formatters.feature +42 -0
- data/test/features/pluggable-languages.feature +15 -0
- data/test/features/require-ruby.feature +38 -0
- data/test/features/step_definitions/cli_steps.rb +46 -0
- data/test/features/step_definitions/format_steps.rb +63 -0
- data/test/features/step_definitions/junit_steps.rb +57 -0
- data/test/features/step_definitions/language_steps.rb +39 -0
- data/test/features/step_definitions/require_steps.rb +38 -0
- data/test/features/support/feature_helper.rb +142 -0
- data/test/fixtures/.keep +0 -0
- data/test/fixtures/files/clean/README.md +6 -0
- data/test/fixtures/files/clean/ansiblish.yaml +12 -0
- data/test/fixtures/files/clean/kitchenish.yml +17 -0
- data/test/fixtures/files/clean/rubocopish.yaml +11 -0
- data/test/fixtures/files/error/bad-indentation.yaml +5 -0
- data/test/fixtures/files/error/missing-array-element.yaml +5 -0
- data/test/fixtures/files/error/unquoted-jinja-template.yaml +3 -0
- data/test/fixtures/files/error/very-high-yaml-version.yaml +3 -0
- data/test/fixtures/require/invalid.rb +6 -0
- data/test/fixtures/require/mock_format.rb +10 -0
- data/test/fixtures/require/second.rb +10 -0
- data/test/fixtures/require/valid.rb +10 -0
- data/test/unit/.keep +0 -0
- data/test/unit/format_factory_spec.rb +46 -0
- data/test/unit/language_factory_spec.rb +46 -0
- data/test/unit/result_spec.rb +18 -0
- data/test/unit/spec_helper.rb +31 -0
- metadata +201 -0
data/rubocop.yml
ADDED
data/test/features/.keep
ADDED
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: Command line help
|
2
|
+
|
3
|
+
In order to be able to learn about the options available for syntax checking
|
4
|
+
As a developer
|
5
|
+
I want to be able to interactively get help on the options from the command line
|
6
|
+
|
7
|
+
Scenario: Command help
|
8
|
+
Given I have installed the tool
|
9
|
+
When I run it on the command line with the help option
|
10
|
+
Then the simple usage text should be displayed along with a zero exit code
|
11
|
+
|
12
|
+
Scenario: Display version
|
13
|
+
Given I have installed the tool
|
14
|
+
When I run it on the command line with the version option
|
15
|
+
Then the current version should be displayed
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Feature: jUnit / SureFire format
|
2
|
+
|
3
|
+
In order to be able to use the tool with many popular CI tools
|
4
|
+
As a developer
|
5
|
+
I want to be able to have output formatted as jUnit-style XML reports
|
6
|
+
|
7
|
+
Scenario: List available core formats
|
8
|
+
Given I have installed the tool
|
9
|
+
When I run it on the command line with the list-formats option
|
10
|
+
Then JUnit should be included in the listed formats
|
11
|
+
|
12
|
+
Scenario: Use JUnit format on clean files
|
13
|
+
Given I have installed the tool
|
14
|
+
When I run it on the command line with the JUnit format on clean files
|
15
|
+
Then I should get a 0 exit code
|
16
|
+
And the generated files should be valid XML
|
17
|
+
And the JUnit files should have the correct structure
|
18
|
+
And the JUnit files should have 0 errors
|
19
|
+
And the JUnit files should have 0 warnings
|
20
|
+
|
21
|
+
Scenario: Use JUnit format on error files
|
22
|
+
Given I have installed the tool
|
23
|
+
When I run it on the command line with the JUnit format on error files
|
24
|
+
Then I should get a 1 exit code
|
25
|
+
And the generated files should be valid XML
|
26
|
+
And the JUnit files should have the correct structure
|
27
|
+
And the JUnit files should have 3 errors
|
28
|
+
And the JUnit files should have 0 warnings
|
29
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Feature: Pluggable formatters
|
2
|
+
|
3
|
+
In order to be able to check YAML files for errors
|
4
|
+
As a developer
|
5
|
+
I want to be able to run the tool on yaml files
|
6
|
+
|
7
|
+
Scenario: Use YAML explicitly on clean files
|
8
|
+
Given I have installed the tool
|
9
|
+
When I run it on the command line specifying the YAML language and clean files
|
10
|
+
Then I should get a 0 exit code
|
11
|
+
And the output should show only files for YAML
|
12
|
+
And the output should have 0 warnings
|
13
|
+
And the output should have 0 errors
|
14
|
+
|
15
|
+
|
16
|
+
Scenario: Run all lamnguages on clean files
|
17
|
+
Given I have installed the tool
|
18
|
+
When I run it on the command line specifying all languages and clean files
|
19
|
+
Then I should get a 0 exit code
|
20
|
+
And the output should include files for YAML
|
21
|
+
And the output should have 0 warnings
|
22
|
+
And the output should have 0 errors
|
23
|
+
|
24
|
+
|
25
|
+
Scenario: Use YAML explicitly on error files
|
26
|
+
Given I have installed the tool
|
27
|
+
When I run it on the command line specifying the YAML language and error files
|
28
|
+
Then I should get a 1 exit code
|
29
|
+
And the output should show only files for YAML
|
30
|
+
And the output should have 0 warnings
|
31
|
+
And the output should have 3 errors
|
32
|
+
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Feature: Pluggable formatters
|
2
|
+
|
3
|
+
In order to be able to use the tool with whatever CI engine I need
|
4
|
+
As a developer
|
5
|
+
I want to be able to output the results of language checks in various formats
|
6
|
+
|
7
|
+
Scenario: List available core formats
|
8
|
+
Given I have installed the tool
|
9
|
+
When I run it on the command line with the list-formats option
|
10
|
+
Then I should get a list of the core formats along with a zero exit code
|
11
|
+
|
12
|
+
Scenario: Reject an invalid format
|
13
|
+
Given I have installed the tool
|
14
|
+
When I run it on the command line with the format option and the argument foo
|
15
|
+
Then I should get an error message and the exit code 4
|
16
|
+
|
17
|
+
Scenario: Reject multiple formats without destination
|
18
|
+
Given I have installed the tool
|
19
|
+
When I run it on the command line with two format options and 0 destinations
|
20
|
+
Then I should get an error message and the exit code 4
|
21
|
+
|
22
|
+
Scenario: Reject multiple formats with ambiguous destination count
|
23
|
+
Given I have installed the tool
|
24
|
+
When I run it on the command line with two format options and 1 destinations
|
25
|
+
Then I should get an error message and the exit code 4
|
26
|
+
|
27
|
+
Scenario: Use multiple formatters with correct number of destinations
|
28
|
+
Given I have installed the tool
|
29
|
+
When I run it on the command line with two format options and 2 destinations
|
30
|
+
Then I should get a 0 exit code
|
31
|
+
|
32
|
+
Scenario: Create destination files automatically
|
33
|
+
Given I have installed the tool
|
34
|
+
When I run it on the command line with a destination in the current directory
|
35
|
+
Then the file should be created
|
36
|
+
|
37
|
+
Scenario: Create destination directories automatically
|
38
|
+
Given I have installed the tool
|
39
|
+
When I run it on the command line with a destination in a directory that does not exist
|
40
|
+
Then the file should be created
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: Pluggable languages
|
2
|
+
|
3
|
+
In order to be able to use the tool on whatever language I need
|
4
|
+
As a developer
|
5
|
+
I want to be able to extend the tool by adding support for new languages
|
6
|
+
|
7
|
+
Scenario: List available core languages
|
8
|
+
Given I have installed the tool
|
9
|
+
When I run it on the command line with the list-languages option
|
10
|
+
Then I should get a list of the core languages along with a zero exit code
|
11
|
+
|
12
|
+
Scenario: Reject an invalid language
|
13
|
+
Given I have installed the tool
|
14
|
+
When I run it on the command line with the lang option and the argument foo
|
15
|
+
Then I should get an error message and the exit code 3
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Feature: Load extra Ruby files
|
2
|
+
|
3
|
+
In order to be able to use the tool with custom plugins
|
4
|
+
As a developer
|
5
|
+
I want to be able to load additional ruby files
|
6
|
+
|
7
|
+
Scenario: Load a valid ruby file
|
8
|
+
Given I have installed the tool
|
9
|
+
When I run it on the command line with the require option and a valid ruby file
|
10
|
+
Then the valid ruby file should have been loaded
|
11
|
+
And I should get a 0 exit code
|
12
|
+
|
13
|
+
Scenario: Reject a missing file with a nice message
|
14
|
+
Given I have installed the tool
|
15
|
+
When I run it on the command line with the require option and a missing ruby file
|
16
|
+
Then I should get an error message about the missing require file
|
17
|
+
And I should get a 5 exit code
|
18
|
+
|
19
|
+
Scenario: Reject an invalid file with a nice message
|
20
|
+
Given I have installed the tool
|
21
|
+
When I run it on the command line with the require option and a invalid ruby file
|
22
|
+
Then I should get an error message about the invalid require file
|
23
|
+
And I should get a 5 exit code
|
24
|
+
|
25
|
+
Scenario: Reject an invalid file with a stacktrace when debug flag is present
|
26
|
+
Given I have installed the tool
|
27
|
+
When I run it on the command line with the require option and the debug option and a invalid ruby file
|
28
|
+
Then I should get a stack trace about the invalid require file
|
29
|
+
And I should get a 5 exit code
|
30
|
+
|
31
|
+
Scenario: Accept multiple requires
|
32
|
+
Given I have installed the tool
|
33
|
+
When I run it on the command line with two requires
|
34
|
+
Then the first ruby file should have been loaded
|
35
|
+
And the second ruby file should have been loaded
|
36
|
+
And I should get a 0 exit code
|
37
|
+
|
38
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Given(/^I have installed the tool$/) do
|
2
|
+
end
|
3
|
+
|
4
|
+
# TODO: Add code to target fixture directory by default
|
5
|
+
When(/^I run it on the command line with the ([^ ]+) option(?: and the argument )?([^ ]+)?$/) do |option, arg|
|
6
|
+
options = []
|
7
|
+
if option.match(/\-\w$/)
|
8
|
+
options << option
|
9
|
+
else
|
10
|
+
options << "--#{option}"
|
11
|
+
end
|
12
|
+
if arg
|
13
|
+
options << arg
|
14
|
+
end
|
15
|
+
@run_result = run_check(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
Then(/^the simple usage text should be displayed along with a non\-zero exit code$/) do
|
19
|
+
refute_empty(@run_result[:stderr], 'Expected to see an error message on STDERR')
|
20
|
+
refute_equal(@run_result[:exit_status], 0)
|
21
|
+
assert_usage_message
|
22
|
+
end
|
23
|
+
|
24
|
+
Then(/^the simple usage text should be displayed along with a zero exit code$/) do
|
25
|
+
assert_empty(@run_result[:stderr])
|
26
|
+
assert_equal(@run_result[:exit_status], 0)
|
27
|
+
assert_usage_message
|
28
|
+
end
|
29
|
+
|
30
|
+
Then(/^the current version should be displayed$/) do
|
31
|
+
assert_match(/^ci-syntax-tool/, @run_result[:stdout], 'Version string should include tool name')
|
32
|
+
assert_match(Regexp.new(Regexp.escape(CI::Syntax::Tool::VERSION)), @run_result[:stdout], 'Version string should include the version')
|
33
|
+
assert_equal(@run_result[:stdout].split("\n").length, 1, 'Version output should be exactly one line long')
|
34
|
+
assert_empty(@run_result[:stderr])
|
35
|
+
assert_equal(@run_result[:exit_status], 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
Then(/^I should get an error message and the exit code (\d+)$/) do |expected_exit_status|
|
39
|
+
refute_empty(@run_result[:stderr], 'Expected to see an error message on STDERR')
|
40
|
+
assert_equal(expected_exit_status.to_i, @run_result[:exit_status])
|
41
|
+
end
|
42
|
+
|
43
|
+
Then(/^I should get a (\d+) exit code$/) do |expected_exit_status|
|
44
|
+
assert_equal(expected_exit_status.to_i, @run_result[:exit_status].to_i)
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
When(/^I run it on the command line with two format options and (\d) destination(?:s)$/) do |dest_count|
|
4
|
+
options = [
|
5
|
+
'--require', Dir.pwd + '/' + 'test/fixtures/require/mock_format.rb',
|
6
|
+
'--format', 'MockFormat',
|
7
|
+
'--format', 'MockFormat',
|
8
|
+
]
|
9
|
+
(0..(dest_count.to_i-1)).to_a.each do |i|
|
10
|
+
options << '--output'
|
11
|
+
options << 'test/tmp/dest-' + i.to_s
|
12
|
+
end
|
13
|
+
options << 'test/fixtures/files/clean'
|
14
|
+
@run_result = run_check(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
When(/^I run it on the command line with a destination in (the current directory|a directory that does not exist)$/) do |mode|
|
18
|
+
path = 'output-create-test-1'
|
19
|
+
options = [
|
20
|
+
'--output'
|
21
|
+
]
|
22
|
+
if mode == 'the current directory'
|
23
|
+
FileUtils.rm_f path
|
24
|
+
options << path
|
25
|
+
else
|
26
|
+
dir = 'test/tmp/output-create-dir'
|
27
|
+
FileUtils.rm_f dir
|
28
|
+
options << dir + '/' + path
|
29
|
+
end
|
30
|
+
options << 'test/fixtures/files/clean'
|
31
|
+
@expected_output_paths = [ path ]
|
32
|
+
@run_result = run_check(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
When(/^I run it on the command line with the ([^ ]+) format on ([^ ]+) files$/) do |fmt_name, fixture|
|
36
|
+
path = "test/tmp/#{fmt_name}.out"
|
37
|
+
FileUtils.rm_f path
|
38
|
+
options = [
|
39
|
+
'--format', fmt_name,
|
40
|
+
'--output', path,
|
41
|
+
'--lang', 'YAML',
|
42
|
+
"test/fixtures/files/#{fixture}",
|
43
|
+
]
|
44
|
+
@expected_output_paths = [ path ]
|
45
|
+
@run_result = run_check(options)
|
46
|
+
end
|
47
|
+
|
48
|
+
Then(/^I should get a list of the core formats along with a zero exit code$/) do
|
49
|
+
assert_equal(0, @run_result[:exit_status])
|
50
|
+
assert_format_list
|
51
|
+
end
|
52
|
+
|
53
|
+
Then(/^([^ ]+) should be included in the listed formats$/) do |fmt_name|
|
54
|
+
actual = @run_result[:stdout].split("\n")
|
55
|
+
assert_includes(actual, fmt_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
Then(/^the file(?:s)? should be created$/) do
|
59
|
+
@expected_output_paths.each do |path|
|
60
|
+
assert(File.exist?(path), "#{path} should exist")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
Then(/^the generated files should be valid XML$/) do
|
4
|
+
@expected_output_paths.each do |path|
|
5
|
+
begin
|
6
|
+
doc = File.open(path) { |f| Nokogiri::XML(f) }
|
7
|
+
pass
|
8
|
+
rescue
|
9
|
+
flunk
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Then(/^the JUnit files should have the correct structure$/) do
|
15
|
+
@expected_output_paths.each do |path|
|
16
|
+
|
17
|
+
# puts "POINT A:" + path
|
18
|
+
|
19
|
+
doc = File.open(path) { |f| Nokogiri::XML(f) }
|
20
|
+
|
21
|
+
# puts "POINT B:" + doc.to_s
|
22
|
+
assert_equal(1, doc.xpath('/testsuites').length, "It should have one root testsuites element")
|
23
|
+
|
24
|
+
# It should have nonzero testsuite elements with a name attribute reflecting the language
|
25
|
+
match = doc.xpath('/testsuites/testsuite')
|
26
|
+
assert(match.length > 0, "It should have nonzero testsuite elements")
|
27
|
+
langs = CI::Syntax::Tool::LanguageFactory.all_language_names
|
28
|
+
match.each do |node|
|
29
|
+
assert_includes(langs, node.at_xpath('@name').to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
# It should have nonzero testcase elements, one per file, with name = sanitized filename
|
33
|
+
match = doc.xpath('/testsuites/testsuite/testcase')
|
34
|
+
assert(match.length > 0, "It should have nonzero testcase elements")
|
35
|
+
match.each do |node|
|
36
|
+
file = node.at_xpath('@name').to_s
|
37
|
+
refute_empty(file, "testcase should have a name attribute")
|
38
|
+
end
|
39
|
+
|
40
|
+
# It may have failure elements which must have a type=warning or error
|
41
|
+
match = doc.xpath('/testsuites/testsuite/testcase/failure')
|
42
|
+
match.each do |node|
|
43
|
+
type = node.at_xpath('@type').to_s
|
44
|
+
assert_includes(['warning', 'error'], type, "Each failure type should be either a warning or an error")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
Then(/^the JUnit files should have (\d+) (error|warning)s$/) do |count, level|
|
51
|
+
actual_count = 0
|
52
|
+
@expected_output_paths.each do |path|
|
53
|
+
doc = File.open(path) { |f| Nokogiri::XML(f) }
|
54
|
+
actual_count += doc.xpath("//failure[@type=\"#{level}\"]").length
|
55
|
+
end
|
56
|
+
assert_equal(count.to_i, actual_count)
|
57
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
When(/^I run it on the command line specifying(?: the)? ([^ ]+) language(?:s)? and ([^ ]+) files$/) do |lang_name, fixture_group|
|
2
|
+
options = [
|
3
|
+
'--require', Dir.pwd + '/' + 'test/fixtures/require/mock_format.rb',
|
4
|
+
'--format', 'MockFormat',
|
5
|
+
]
|
6
|
+
unless lang_name == 'all'
|
7
|
+
options << '--lang'
|
8
|
+
options << lang_name
|
9
|
+
end
|
10
|
+
options << 'test/fixtures/files/' + fixture_group
|
11
|
+
|
12
|
+
@run_result = run_check(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
Then(/^I should get a list of the core languages along with a zero exit code$/) do
|
16
|
+
assert_equal(0, @run_result[:exit_status].to_i)
|
17
|
+
assert_language_list
|
18
|
+
end
|
19
|
+
|
20
|
+
Then(/^the output should have (\d+) (error|warning)s$/) do |count, level|
|
21
|
+
actual = @run_result[:overall_result].send((level +'_count').to_sym)
|
22
|
+
assert_equal(count.to_i, actual)
|
23
|
+
end
|
24
|
+
|
25
|
+
Then(/^the output should show only files for ([^ ]+)$/) do |lang_name|
|
26
|
+
touched = @run_result[:overall_result].file_paths
|
27
|
+
matched = files_matching_language(touched, lang_name)
|
28
|
+
extra = touched - matched
|
29
|
+
assert_empty(extra, "The touched files should ONLY include files for #{lang_name}")
|
30
|
+
refute_empty(matched, "The touched files should include files for #{lang_name}")
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
Then(/^the output should include files for ([^ ]+)$/) do | lang_name|
|
35
|
+
touched = @run_result[:overall_result].file_paths
|
36
|
+
matched = files_matching_language(touched, lang_name)
|
37
|
+
refute_empty(matched, "The touched files should include files for #{lang_name}")
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
When(/^I run it on the command line with the require option (and the debug option )?and a (valid|invalid|missing) ruby file$/) do |need_debug, require_file|
|
2
|
+
options = [
|
3
|
+
'--require', Dir.pwd + '/' + 'test/fixtures/require/' + require_file + '.rb',
|
4
|
+
]
|
5
|
+
if need_debug then
|
6
|
+
options << '--debug'
|
7
|
+
end
|
8
|
+
options << 'test/fixtures/files/clean'
|
9
|
+
@run_result = run_check(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
When(/^I run it on the command line with two requires$/) do
|
13
|
+
options = [
|
14
|
+
'--require', Dir.pwd + '/' + 'test/fixtures/require/valid.rb',
|
15
|
+
'--require', Dir.pwd + '/' + 'test/fixtures/require/second.rb',
|
16
|
+
]
|
17
|
+
options << 'test/fixtures/files/clean'
|
18
|
+
@run_result = run_check(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
Then(/^I should get an error message about the (missing|invalid) require file$/) do |which_require|
|
22
|
+
assert_match(Regexp.new("Could not load .+ because it appears to be #{which_require}."), @run_result[:stderr])
|
23
|
+
assert_equal(1, @run_result[:stderr].split("\n").length, 'Stderr should be a single line, not a nasty stacktrace')
|
24
|
+
assert_empty(@run_result[:stdout], 'Stdout should be silent')
|
25
|
+
end
|
26
|
+
|
27
|
+
Then(/^I should get a stack trace about the invalid require file$/) do
|
28
|
+
assert_match(Regexp.new("Could not load .+ because it appears to be invalid."), @run_result[:stderr])
|
29
|
+
assert_match(Regexp.new('(Error|Exception)'),@run_result[:stderr], 'Stderr should include a stack trace')
|
30
|
+
assert_empty(@run_result[:stdout], 'Stdout should be silent')
|
31
|
+
end
|
32
|
+
|
33
|
+
Then(/^the (valid|first|second) ruby file should have been loaded$/) do |which_require|
|
34
|
+
klass_name = which_require == 'first' ? 'valid' : which_require
|
35
|
+
klass_name.capitalize!
|
36
|
+
klass_name = 'CI::Syntax::Tool::Test::' + klass_name + 'Require'
|
37
|
+
assert_class_loaded(klass_name)
|
38
|
+
end
|