reek 1.2.2 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Rakefile +0 -1
- data/config/defaults.reek +4 -6
- data/features/masking_smells.feature +9 -9
- data/features/options.feature +2 -2
- data/features/profile.feature +34 -0
- data/features/rake_task.feature +74 -0
- data/features/reports.feature +1 -1
- data/features/samples.feature +4 -4
- data/features/stdin.feature +1 -1
- data/features/step_definitions/reek_steps.rb +11 -7
- data/features/support/env.rb +26 -18
- data/lib/reek.rb +1 -1
- data/lib/reek/adapters/application.rb +9 -2
- data/lib/reek/adapters/command_line.rb +2 -2
- data/lib/reek/adapters/source.rb +4 -1
- data/lib/reek/adapters/spec.rb +1 -3
- data/lib/reek/block_context.rb +14 -8
- data/lib/reek/class_context.rb +6 -66
- data/lib/reek/code_context.rb +10 -0
- data/lib/reek/code_parser.rb +25 -53
- data/lib/reek/configuration.rb +12 -6
- data/lib/reek/if_context.rb +2 -3
- data/lib/reek/method_context.rb +3 -12
- data/lib/reek/module_context.rb +30 -22
- data/lib/reek/name.rb +2 -0
- data/lib/reek/object_refs.rb +0 -3
- data/lib/reek/sexp_formatter.rb +0 -2
- data/lib/reek/smells/class_variable.rb +17 -4
- data/lib/reek/smells/control_couple.rb +3 -10
- data/lib/reek/smells/data_clump.rb +10 -10
- data/lib/reek/smells/feature_envy.rb +1 -8
- data/lib/reek/smells/large_class.rb +3 -3
- data/lib/reek/smells/simulated_polymorphism.rb +17 -3
- data/lib/reek/smells/smell_detector.rb +11 -2
- data/lib/reek/smells/utility_function.rb +1 -1
- data/lib/reek/sniffer.rb +0 -8
- data/lib/reek/stop_context.rb +1 -1
- data/lib/reek/tree_dresser.rb +74 -0
- data/reek.gemspec +3 -3
- data/spec/reek/block_context_spec.rb +6 -6
- data/spec/reek/class_context_spec.rb +2 -23
- data/spec/reek/code_context_spec.rb +149 -67
- data/spec/reek/code_parser_spec.rb +0 -102
- data/spec/reek/method_context_spec.rb +4 -4
- data/spec/reek/singleton_method_context_spec.rb +1 -1
- data/spec/reek/smells/attribute_spec.rb +1 -1
- data/spec/reek/smells/class_variable_spec.rb +96 -7
- data/spec/reek/smells/control_couple_spec.rb +1 -1
- data/spec/reek/smells/data_clump_spec.rb +31 -13
- data/spec/reek/smells/feature_envy_spec.rb +1 -1
- data/spec/reek/smells/large_class_spec.rb +32 -18
- data/spec/reek/smells/simulated_polymorphism_spec.rb +66 -18
- data/spec/reek/sniffer_spec.rb +1 -0
- data/spec/samples/not_quite_masked/dirty.rb +2 -0
- data/spec/spec_helper.rb +1 -1
- data/tasks/reek.rake +1 -1
- metadata +5 -3
- data/lib/reek/exceptions.reek +0 -20
data/History.txt
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
=== Minor Changes
|
4
4
|
* New smell: Attribute (disabled by default)
|
5
|
+
* Expanded DataClump to check modules (#9)
|
6
|
+
* Fixed LargeClass to ignore inner classes and modules
|
7
|
+
* Fixed LargeClass to ignore singleton methods
|
5
8
|
* Removed support for MyClass.should_not reek due to ParseTree EOL
|
9
|
+
* Removed internal requiring of 'rubygems'
|
6
10
|
|
7
11
|
== 1.2.1 (2009-10-02)
|
8
12
|
|
data/Rakefile
CHANGED
data/config/defaults.reek
CHANGED
@@ -14,12 +14,11 @@ LongParameterList:
|
|
14
14
|
initialize:
|
15
15
|
max_params: 5
|
16
16
|
FeatureEnvy:
|
17
|
-
exclude:
|
18
|
-
- initialize
|
19
|
-
enabled: true
|
20
|
-
ClassVariable:
|
21
17
|
exclude: &id001 []
|
22
18
|
|
19
|
+
enabled: true
|
20
|
+
ClassVariable:
|
21
|
+
exclude: *id001
|
23
22
|
enabled: true
|
24
23
|
UncommunicativeName:
|
25
24
|
accept:
|
@@ -64,8 +63,7 @@ DataClump:
|
|
64
63
|
max_copies: 2
|
65
64
|
min_clump_size: 2
|
66
65
|
ControlCouple:
|
67
|
-
exclude:
|
68
|
-
- initialize
|
66
|
+
exclude: *id001
|
69
67
|
enabled: true
|
70
68
|
LongYieldList:
|
71
69
|
max_params: 3
|
@@ -6,7 +6,7 @@ Feature: Masking smells using config files
|
|
6
6
|
|
7
7
|
Scenario: empty config file is ignored
|
8
8
|
When I run reek spec/samples/empty_config_file/dirty.rb
|
9
|
-
Then
|
9
|
+
Then the exit status indicates smells
|
10
10
|
And it reports:
|
11
11
|
"""
|
12
12
|
spec/samples/empty_config_file/dirty.rb -- 6 warnings:
|
@@ -21,17 +21,17 @@ Feature: Masking smells using config files
|
|
21
21
|
|
22
22
|
Scenario: corrupt config file prevents normal output
|
23
23
|
When I run reek spec/samples/corrupt_config_file/dirty.rb
|
24
|
-
Then
|
24
|
+
Then the exit status indicates an error
|
25
25
|
And it reports the error 'Error: Invalid configuration file "corrupt.reek" -- not a Hash'
|
26
26
|
|
27
27
|
Scenario: missing source file is an error
|
28
28
|
When I run reek no_such_file.rb
|
29
|
-
Then
|
29
|
+
Then the exit status indicates an error
|
30
30
|
And it reports the error "Error: No such file or directory - no_such_file.rb"
|
31
31
|
|
32
32
|
Scenario: switch off one smell
|
33
33
|
When I run reek spec/samples/masked/dirty.rb
|
34
|
-
Then
|
34
|
+
Then the exit status indicates smells
|
35
35
|
And it reports:
|
36
36
|
"""
|
37
37
|
spec/samples/masked/dirty.rb -- 3 warnings (+3 masked):
|
@@ -43,7 +43,7 @@ Feature: Masking smells using config files
|
|
43
43
|
|
44
44
|
Scenario: switch off one smell but show all in the report
|
45
45
|
When I run reek --show-all spec/samples/masked/dirty.rb
|
46
|
-
Then
|
46
|
+
Then the exit status indicates smells
|
47
47
|
And it reports:
|
48
48
|
"""
|
49
49
|
spec/samples/masked/dirty.rb -- 3 warnings (+3 masked):
|
@@ -58,7 +58,7 @@ Feature: Masking smells using config files
|
|
58
58
|
|
59
59
|
Scenario: switch off one smell and hide them in the report
|
60
60
|
When I run reek --no-show-all spec/samples/masked/dirty.rb
|
61
|
-
Then
|
61
|
+
Then the exit status indicates smells
|
62
62
|
And it reports:
|
63
63
|
"""
|
64
64
|
spec/samples/masked/dirty.rb -- 3 warnings (+3 masked):
|
@@ -70,7 +70,7 @@ Feature: Masking smells using config files
|
|
70
70
|
|
71
71
|
Scenario: non-masked smells are only counted once
|
72
72
|
When I run reek spec/samples/not_quite_masked/dirty.rb
|
73
|
-
Then
|
73
|
+
Then the exit status indicates smells
|
74
74
|
And it reports:
|
75
75
|
"""
|
76
76
|
spec/samples/not_quite_masked/dirty.rb -- 5 warnings (+1 masked):
|
@@ -85,7 +85,7 @@ Feature: Masking smells using config files
|
|
85
85
|
@overrides
|
86
86
|
Scenario: lower overrides upper
|
87
87
|
When I run reek spec/samples/overrides
|
88
|
-
Then
|
88
|
+
Then the exit status indicates smells
|
89
89
|
And it reports:
|
90
90
|
"""
|
91
91
|
spec/samples/overrides/masked/dirty.rb -- 2 warnings (+4 masked):
|
@@ -97,7 +97,7 @@ Feature: Masking smells using config files
|
|
97
97
|
@overrides
|
98
98
|
Scenario: all show up masked even when overridden
|
99
99
|
When I run reek --show-all spec/samples/overrides
|
100
|
-
Then
|
100
|
+
Then the exit status indicates smells
|
101
101
|
And it reports:
|
102
102
|
"""
|
103
103
|
spec/samples/overrides/masked/dirty.rb -- 2 warnings (+4 masked):
|
data/features/options.feature
CHANGED
@@ -6,12 +6,12 @@ Feature: Reek can be controlled using command-line options
|
|
6
6
|
|
7
7
|
Scenario: return non-zero status on bad option
|
8
8
|
When I run reek --no-such-option
|
9
|
-
Then
|
9
|
+
Then the exit status indicates an error
|
10
10
|
And it reports the error "Error: invalid option: --no-such-option"
|
11
11
|
|
12
12
|
Scenario: return non-zero status on missing argument
|
13
13
|
When I run reek -f
|
14
|
-
Then
|
14
|
+
Then the exit status indicates an error
|
15
15
|
And it reports the error "Error: missing argument: -f"
|
16
16
|
|
17
17
|
Scenario: display the current version number
|
@@ -0,0 +1,34 @@
|
|
1
|
+
@profile
|
2
|
+
Feature: Reek's configuration can be based on any number of canned profiles
|
3
|
+
The starting point for configuring the smell detectors can be
|
4
|
+
selected from a list of supplied config files.
|
5
|
+
|
6
|
+
# Scenario: XP profile reveals Attribute smells
|
7
|
+
# When I run reek --profile xp spec/samples/not_quite_masked/dirty.rb
|
8
|
+
# Then the exit status indicates smells
|
9
|
+
# And it reports:
|
10
|
+
# """
|
11
|
+
# spec/samples/not_quite_masked/dirty.rb -- 5 warnings (+2 masked):
|
12
|
+
# Dirty declares the attribute property (Attribute)
|
13
|
+
# Dirty has the variable name '@s' (Uncommunicative Name)
|
14
|
+
# Dirty#a calls @s.title twice (Duplication)
|
15
|
+
# Dirty#a calls puts(@s.title) twice (Duplication)
|
16
|
+
# Dirty#a has the name 'a' (Uncommunicative Name)
|
17
|
+
#
|
18
|
+
# """
|
19
|
+
#
|
20
|
+
# Scenario: XP profile works with unmasked smells
|
21
|
+
# When I run reek --show-all --profile xp spec/samples/not_quite_masked/dirty.rb
|
22
|
+
# Then the exit status indicates smells
|
23
|
+
# And it reports:
|
24
|
+
# """
|
25
|
+
# spec/samples/not_quite_masked/dirty.rb -- 5 warnings (+2 masked):
|
26
|
+
# Dirty declares the attribute property (Attribute)
|
27
|
+
# Dirty has the variable name '@s' (Uncommunicative Name)
|
28
|
+
# Dirty#a calls @s.title twice (Duplication)
|
29
|
+
# Dirty#a calls puts(@s.title) twice (Duplication)
|
30
|
+
# Dirty#a has the name 'a' (Uncommunicative Name)
|
31
|
+
# (masked) Dirty#a/block has the variable name 'x' (Uncommunicative Name)
|
32
|
+
# (masked) Dirty#a/block/block is nested (Nested Iterators)
|
33
|
+
#
|
34
|
+
# """
|
@@ -0,0 +1,74 @@
|
|
1
|
+
@rake
|
2
|
+
Feature: Reek can be driven through its RakeTask
|
3
|
+
Reek provides an easy way to integrate its use into Rakefiles,
|
4
|
+
via the RakeTask class. These scenarios test its various options.
|
5
|
+
|
6
|
+
Scenario: source_files points at the desired files
|
7
|
+
When I run rake reek with:
|
8
|
+
"""
|
9
|
+
Reek::RakeTask.new do |t|
|
10
|
+
t.source_files = 'spec/samples/masked/dirty.rb'
|
11
|
+
end
|
12
|
+
"""
|
13
|
+
Then the exit status indicates an error
|
14
|
+
And it reports:
|
15
|
+
"""
|
16
|
+
spec/samples/masked/dirty.rb -- 3 warnings (+3 masked):
|
17
|
+
Dirty#a calls @s.title twice (Duplication)
|
18
|
+
Dirty#a calls puts(@s.title) twice (Duplication)
|
19
|
+
Dirty#a/block/block is nested (Nested Iterators)
|
20
|
+
"""
|
21
|
+
|
22
|
+
Scenario: name changes the task name
|
23
|
+
When I run rake silky with:
|
24
|
+
"""
|
25
|
+
Reek::RakeTask.new('silky') do |t|
|
26
|
+
t.source_files = 'spec/samples/masked/dirty.rb'
|
27
|
+
end
|
28
|
+
"""
|
29
|
+
Then the exit status indicates an error
|
30
|
+
And it reports:
|
31
|
+
"""
|
32
|
+
spec/samples/masked/dirty.rb -- 3 warnings (+3 masked):
|
33
|
+
Dirty#a calls @s.title twice (Duplication)
|
34
|
+
Dirty#a calls puts(@s.title) twice (Duplication)
|
35
|
+
Dirty#a/block/block is nested (Nested Iterators)
|
36
|
+
"""
|
37
|
+
|
38
|
+
Scenario: verbose prints the reek command
|
39
|
+
When I run rake reek with:
|
40
|
+
"""
|
41
|
+
Reek::RakeTask.new do |t|
|
42
|
+
t.source_files = 'spec/samples/masked/dirty.rb'
|
43
|
+
t.verbose = true
|
44
|
+
end
|
45
|
+
"""
|
46
|
+
Then the exit status indicates an error
|
47
|
+
And it reports:
|
48
|
+
"""
|
49
|
+
/usr/bin/ruby1.8 -I"/home/kevin/Working/git/reek/lib" "/home/kevin/Working/git/reek/bin/reek" "spec/samples/masked/dirty.rb"
|
50
|
+
spec/samples/masked/dirty.rb -- 3 warnings (+3 masked):
|
51
|
+
Dirty#a calls @s.title twice (Duplication)
|
52
|
+
Dirty#a calls puts(@s.title) twice (Duplication)
|
53
|
+
Dirty#a/block/block is nested (Nested Iterators)
|
54
|
+
"""
|
55
|
+
|
56
|
+
Scenario: fail_on_error can hide the error status
|
57
|
+
When I run rake reek with:
|
58
|
+
"""
|
59
|
+
Reek::RakeTask.new do |t|
|
60
|
+
t.fail_on_error = false
|
61
|
+
t.source_files = 'spec/samples/empty_config_file/dirty.rb'
|
62
|
+
end
|
63
|
+
"""
|
64
|
+
Then it succeeds
|
65
|
+
And it reports:
|
66
|
+
"""
|
67
|
+
spec/samples/empty_config_file/dirty.rb -- 6 warnings:
|
68
|
+
Dirty has the variable name '@s' (Uncommunicative Name)
|
69
|
+
Dirty#a calls @s.title twice (Duplication)
|
70
|
+
Dirty#a calls puts(@s.title) twice (Duplication)
|
71
|
+
Dirty#a has the name 'a' (Uncommunicative Name)
|
72
|
+
Dirty#a/block has the variable name 'x' (Uncommunicative Name)
|
73
|
+
Dirty#a/block/block is nested (Nested Iterators)
|
74
|
+
"""
|
data/features/reports.feature
CHANGED
@@ -6,7 +6,7 @@ Feature: Correctly formatted reports
|
|
6
6
|
|
7
7
|
Scenario Outline: two reports run together with indented smells
|
8
8
|
When I run reek <args>
|
9
|
-
Then
|
9
|
+
Then the exit status indicates smells
|
10
10
|
And it reports:
|
11
11
|
"""
|
12
12
|
spec/samples/two_smelly_files/dirty_one.rb -- 6 warnings:
|
data/features/samples.feature
CHANGED
@@ -6,7 +6,7 @@ Feature: Basic smell detection
|
|
6
6
|
|
7
7
|
Scenario: Correct smells from inline.rb
|
8
8
|
When I run reek spec/samples/inline.rb
|
9
|
-
Then
|
9
|
+
Then the exit status indicates smells
|
10
10
|
And it reports:
|
11
11
|
"""
|
12
12
|
spec/samples/inline.rb -- 39 warnings (+1 masked):
|
@@ -54,11 +54,11 @@ Feature: Basic smell detection
|
|
54
54
|
|
55
55
|
Scenario: Correct smells from optparse.rb
|
56
56
|
When I run reek spec/samples/optparse.rb
|
57
|
-
Then
|
57
|
+
Then the exit status indicates smells
|
58
58
|
And it reports:
|
59
59
|
"""
|
60
60
|
spec/samples/optparse.rb -- 121 warnings:
|
61
|
-
OptionParser has at least
|
61
|
+
OptionParser has at least 42 methods (Large Class)
|
62
62
|
OptionParser tests ((argv.size == 1) and Array.===(argv[0])) at least 3 times (Simulated Polymorphism)
|
63
63
|
OptionParser tests a at least 7 times (Simulated Polymorphism)
|
64
64
|
OptionParser tests default_pattern at least 7 times (Simulated Polymorphism)
|
@@ -184,7 +184,7 @@ Feature: Basic smell detection
|
|
184
184
|
|
185
185
|
Scenario: Correct smells from redcloth.rb
|
186
186
|
When I run reek spec/samples/redcloth.rb
|
187
|
-
Then
|
187
|
+
Then the exit status indicates smells
|
188
188
|
And it reports:
|
189
189
|
"""
|
190
190
|
spec/samples/redcloth.rb -- 95 warnings:
|
data/features/stdin.feature
CHANGED
@@ -33,7 +33,7 @@ Feature: Reek reads from $stdin when no files are given
|
|
33
33
|
|
34
34
|
Scenario: return non-zero status when there are smells
|
35
35
|
When I pass "class Turn; def y() @x = 3; end end" to reek
|
36
|
-
Then
|
36
|
+
Then the exit status indicates smells
|
37
37
|
And it reports:
|
38
38
|
"""
|
39
39
|
$stdin -- 2 warnings:
|
@@ -1,13 +1,13 @@
|
|
1
1
|
When /^I run reek (.*)$/ do |args|
|
2
|
-
|
2
|
+
reek(args)
|
3
3
|
end
|
4
4
|
|
5
5
|
When /^I pass "([^\"]*)" to reek *(.*)$/ do |stdin, args|
|
6
|
-
|
6
|
+
reek_with_pipe(stdin, args)
|
7
7
|
end
|
8
8
|
|
9
|
-
When /^I run rake
|
10
|
-
rake
|
9
|
+
When /^I run rake (\w*) with:$/ do |name, task_def|
|
10
|
+
rake(name, task_def)
|
11
11
|
end
|
12
12
|
|
13
13
|
Then /^stdout equals "([^\"]*)"$/ do |report|
|
@@ -15,11 +15,15 @@ Then /^stdout equals "([^\"]*)"$/ do |report|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
Then /^it succeeds$/ do
|
18
|
-
@last_exit_status.should ==
|
18
|
+
@last_exit_status.should == Reek::EXIT_STATUS[:success]
|
19
19
|
end
|
20
20
|
|
21
|
-
Then /^
|
22
|
-
@last_exit_status.should ==
|
21
|
+
Then /^the exit status indicates an error$/ do
|
22
|
+
@last_exit_status.should == Reek::EXIT_STATUS[:error]
|
23
|
+
end
|
24
|
+
|
25
|
+
Then /^the exit status indicates smells$/ do
|
26
|
+
@last_exit_status.should == Reek::EXIT_STATUS[:smells]
|
23
27
|
end
|
24
28
|
|
25
29
|
Then /^it reports:$/ do |report|
|
data/features/support/env.rb
CHANGED
@@ -5,34 +5,42 @@ require 'tempfile'
|
|
5
5
|
require 'spec/expectations'
|
6
6
|
require 'fileutils'
|
7
7
|
require 'reek'
|
8
|
+
require 'reek/adapters/application'
|
8
9
|
|
9
|
-
class
|
10
|
-
|
11
|
-
|
12
|
-
stderr_file = Tempfile.new('cucumber')
|
10
|
+
class ReekWorld
|
11
|
+
def run(cmd)
|
12
|
+
stderr_file = Tempfile.new('reek-world')
|
13
13
|
stderr_file.close
|
14
|
-
@last_stdout =
|
14
|
+
@last_stdout = `#{cmd} 2> #{stderr_file.path}`
|
15
15
|
@last_exit_status = $?.exitstatus
|
16
16
|
@last_stderr = IO.read(stderr_file.path)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
stderr_file.close
|
22
|
-
@last_stdout = `echo \"#{stdin}\" | ruby -Ilib bin/reek #{args} 2> #{stderr_file.path}`
|
23
|
-
@last_exit_status = $?.exitstatus
|
24
|
-
@last_stderr = IO.read(stderr_file.path)
|
19
|
+
def reek(args)
|
20
|
+
run("ruby -Ilib -rubygems bin/reek #{args}")
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
23
|
+
def reek_with_pipe(stdin, args)
|
24
|
+
run("echo \"#{stdin}\" | ruby -Ilib -rubygems bin/reek #{args}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def rake(name, task_def)
|
28
|
+
header = <<EOS
|
29
|
+
$:.unshift('lib')
|
30
|
+
require 'reek/adapters/rake_task'
|
31
|
+
|
32
|
+
EOS
|
33
|
+
rakefile = Tempfile.new('rake_task', '.')
|
34
|
+
rakefile.puts(header + task_def)
|
35
|
+
rakefile.close
|
36
|
+
run("RUBYOPT=rubygems rake -f #{rakefile.path} #{name}")
|
37
|
+
lines = @last_stdout.split("\n")
|
38
|
+
if lines.length > 0 and lines[0] =~ /^\(/
|
39
|
+
@last_stdout = lines[1..-1].join("\n")
|
40
|
+
end
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
36
44
|
World do
|
37
|
-
|
45
|
+
ReekWorld.new
|
38
46
|
end
|
data/lib/reek.rb
CHANGED
@@ -3,6 +3,13 @@ require 'reek/adapters/source'
|
|
3
3
|
require 'reek/adapters/core_extras'
|
4
4
|
|
5
5
|
module Reek
|
6
|
+
|
7
|
+
EXIT_STATUS = {
|
8
|
+
:success => 0,
|
9
|
+
:error => 1,
|
10
|
+
:smells => 2
|
11
|
+
}
|
12
|
+
|
6
13
|
#
|
7
14
|
# Represents an instance of a Reek application.
|
8
15
|
# This is the entry point for all invocations of Reek from the
|
@@ -27,7 +34,7 @@ module Reek
|
|
27
34
|
def reek
|
28
35
|
examine_sources
|
29
36
|
puts @options.create_report(@sniffer.sniffers).report
|
30
|
-
return @sniffer.smelly? ?
|
37
|
+
return EXIT_STATUS[@sniffer.smelly? ? :smells : :success]
|
31
38
|
end
|
32
39
|
|
33
40
|
def execute
|
@@ -37,7 +44,7 @@ module Reek
|
|
37
44
|
return ex.status
|
38
45
|
rescue Exception => error
|
39
46
|
$stderr.puts "Error: #{error}"
|
40
|
-
return
|
47
|
+
return EXIT_STATUS[:error]
|
41
48
|
end
|
42
49
|
end
|
43
50
|
end
|
@@ -45,11 +45,11 @@ EOB
|
|
45
45
|
|
46
46
|
@parser.on("-h", "--help", "Show this message") do
|
47
47
|
puts @parser
|
48
|
-
exit(
|
48
|
+
exit(EXIT_STATUS[:success])
|
49
49
|
end
|
50
50
|
@parser.on("-v", "--version", "Show version") do
|
51
51
|
puts "#{@parser.program_name} #{Reek::VERSION}"
|
52
|
-
exit(
|
52
|
+
exit(EXIT_STATUS[:success])
|
53
53
|
end
|
54
54
|
|
55
55
|
@parser.separator "\nReport formatting:"
|