chutney 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -2
  3. data/Gemfile +2 -0
  4. data/README.md +6 -1
  5. data/Rakefile +2 -0
  6. data/chutney.gemspec +13 -6
  7. data/config/{chutney.yml → chutney_defaults.yml} +2 -0
  8. data/config/cucumber.yml +1 -0
  9. data/docs/usage/rules.md +7 -1
  10. data/exe/chutney +23 -3
  11. data/lib/chutney.rb +26 -22
  12. data/lib/chutney/configuration.rb +9 -2
  13. data/lib/chutney/formatter.rb +6 -5
  14. data/lib/chutney/formatter/json_formatter.rb +2 -0
  15. data/lib/chutney/formatter/pie_formatter.rb +11 -10
  16. data/lib/chutney/formatter/rainbow_formatter.rb +13 -13
  17. data/lib/chutney/issue.rb +2 -0
  18. data/lib/chutney/linter.rb +87 -83
  19. data/lib/chutney/linter/avoid_full_stop.rb +4 -4
  20. data/lib/chutney/linter/avoid_outline_for_single_example.rb +7 -5
  21. data/lib/chutney/linter/avoid_scripting.rb +8 -6
  22. data/lib/chutney/linter/avoid_typographers_quotes.rb +16 -14
  23. data/lib/chutney/linter/background_does_more_than_setup.rb +8 -7
  24. data/lib/chutney/linter/background_requires_multiple_scenarios.rb +7 -4
  25. data/lib/chutney/linter/bad_scenario_name.rb +6 -4
  26. data/lib/chutney/linter/empty_feature_file.rb +10 -0
  27. data/lib/chutney/linter/file_name_differs_feature_name.rb +7 -5
  28. data/lib/chutney/linter/givens_after_background.rb +7 -8
  29. data/lib/chutney/linter/invalid_file_name.rb +3 -1
  30. data/lib/chutney/linter/invalid_step_flow.rb +9 -9
  31. data/lib/chutney/linter/missing_example_name.rb +9 -9
  32. data/lib/chutney/linter/missing_feature_description.rb +6 -3
  33. data/lib/chutney/linter/missing_feature_name.rb +6 -3
  34. data/lib/chutney/linter/missing_scenario_name.rb +4 -6
  35. data/lib/chutney/linter/missing_test_action.rb +4 -2
  36. data/lib/chutney/linter/missing_verification.rb +4 -2
  37. data/lib/chutney/linter/required_tags_starts_with.rb +7 -6
  38. data/lib/chutney/linter/same_tag_for_all_scenarios.rb +20 -19
  39. data/lib/chutney/linter/scenario_names_match.rb +6 -6
  40. data/lib/chutney/linter/tag_used_multiple_times.rb +3 -1
  41. data/lib/chutney/linter/too_clumsy.rb +4 -2
  42. data/lib/chutney/linter/too_long_step.rb +6 -4
  43. data/lib/chutney/linter/too_many_different_tags.rb +10 -8
  44. data/lib/chutney/linter/too_many_steps.rb +6 -4
  45. data/lib/chutney/linter/too_many_tags.rb +5 -3
  46. data/lib/chutney/linter/unique_scenario_names.rb +5 -5
  47. data/lib/chutney/linter/unknown_variable.rb +15 -15
  48. data/lib/chutney/linter/unused_variable.rb +15 -16
  49. data/lib/chutney/linter/use_background.rb +20 -19
  50. data/lib/chutney/linter/use_outline.rb +15 -14
  51. data/lib/chutney/version.rb +3 -1
  52. data/lib/config/locales/en.yml +2 -0
  53. data/spec/chutney_spec.rb +11 -9
  54. data/spec/spec_helper.rb +2 -0
  55. metadata +19 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b678f90aea391f8707bda58ec5e2f6caf9b5d1f1bf5b1503c6862d4023d5f18
4
- data.tar.gz: ecb2e762fa06c882869ff007e80261137816f431a78c07257d2f140e8f8f6f78
3
+ metadata.gz: a489eb526fae6a79ff386986463cf473aa4f5bc353448e99338781556f7fd194
4
+ data.tar.gz: 5cd7a3264aba055c8fe738147edfa661f57e2c0e258d5991b42adbfb2af45f05
5
5
  SHA512:
6
- metadata.gz: 5f1f5c793a5d0080320606d56471f8ebcb7b0c3ceedca3e5e492eb877d1f0582aef997cd686c9e4782afeff407fb0346a0acbee288bca7306b39f2d8dc951540
7
- data.tar.gz: 362d6bf5c6a1cb8c14a005534259e0ad5592df046a6dc3f747562b0b1875ee22e19b2076ff60873ca54967e2bd2188513566c36d0433ccd1c886fbc73b5c71ab
6
+ metadata.gz: 9fb8325c116764fadd900b0b98961eca7cf95f49ac171ef64e77e29f1806d6ff78bcca90ea9e2119320d33d81b13e67749fb3bf8e916df13e154871b54602b35
7
+ data.tar.gz: 8290dd430b9b55c900179717c3b3fcd83ea805e9142751dec8c69ae4c1e2233a3d5392d8951705a3fed0678b3a9fd768941889b5e9086465fa54ced8c43b1811
@@ -8,7 +8,7 @@
8
8
 
9
9
  # Offense count: 1
10
10
  Metrics/AbcSize:
11
- Max: 16
11
+ Enabled: false
12
12
 
13
13
  # Offense count: 1
14
14
  # Configuration parameters: CountComments.
@@ -52,7 +52,13 @@ Layout/HeredocIndentation:
52
52
  - "**/*_spec.rb"
53
53
 
54
54
  Layout/TrailingWhitespace:
55
- Enabled: false
55
+ Enabled: true
56
56
 
57
57
  Style/FrozenStringLiteralComment:
58
+ Enabled: true
59
+
60
+ Style/StringConcatenation:
58
61
  Enabled: false
62
+
63
+ AllCops:
64
+ NewCops: enable
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/README.md CHANGED
@@ -12,10 +12,15 @@
12
12
  <div align="center">
13
13
 
14
14
  [![Gem Version](https://badge.fury.io/rb/chutney.svg)](https://badge.fury.io/rb/chutney)
15
+ [![Downloads](https://img.shields.io/gem/dt/chutney)](https://rubygems.org/gems/chutney)
15
16
  ![CircleCI branch](https://img.shields.io/circleci/project/github/BillyRuffian/chutney/master.svg?style=flat-square)
16
17
  [![CodeFactor](https://www.codefactor.io/repository/github/billyruffian/chutney/badge?style=flat-square)](https://www.codefactor.io/repository/github/billyruffian/chutney)
17
18
  ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/BillyRuffian/chutney.svg?style=flat-square)
18
19
 
19
20
  </div>
20
21
 
21
- Read the documentation [here](https://billyruffian.github.io/chutney/).
22
+ Read the documentation [here](https://billyruffian.github.io/chutney/) or try [Chutney Online](https://chutney.billy-ruffian.co.uk).
23
+
24
+ ## Notes
25
+
26
+ Chutney 3+ (in beta) has replaced its direct dependency on Cucumber and instead uses the excellent [cuke_modeller](https://github.com/enkessler/cuke_modeler) to parse your feature files.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake/testtask'
2
4
 
3
5
  task default: :build
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Disable rubocop checks for the .gemspec
2
4
  # I'll take the output from 'bundle gem new' to be authoritative
3
5
  # rubocop:disable all
@@ -12,9 +14,13 @@ Gem::Specification.new do |spec|
12
14
  spec.authors = ['Nigel Brookes-Thomas', 'Stefan Rohe', 'Nishtha Argawal', 'John Gluck']
13
15
  spec.email = ['nigel@brookes-thomas.co.uk']
14
16
 
15
- spec.summary = 'A linter for English language Gherkin'
16
- spec.description = 'A linter for your Cucumber features. ' \
17
- 'It supports any spoken language Cucumber v3 supports.'
17
+ spec.summary = 'A linter for multi-lingual Gherkin'
18
+ spec.description = 'A linter for your Cucumber features. ' \
19
+ 'Making sure you have nice, expressible Gherkin is ' \
20
+ 'essential is making sure you have a readable test-base. ' \
21
+ 'Chutney is designed to sniff out smells in your feature ' \
22
+ 'files. ' \
23
+ 'It supports any spoken language Cucumber supports.'
18
24
 
19
25
  spec.homepage = 'https://billyruffian.github.io/chutney/'
20
26
  spec.license = 'MIT'
@@ -43,19 +49,20 @@ Gem::Specification.new do |spec|
43
49
  spec.require_paths = ['lib']
44
50
 
45
51
  spec.add_runtime_dependency 'amatch', '~> 0.4.0'
46
- spec.add_runtime_dependency 'gherkin', '~> 5.1.0'
52
+ spec.add_runtime_dependency 'cuke_modeler', '~> 3.3'
47
53
  spec.add_runtime_dependency 'i18n', '~> 1.8.2'
48
54
  spec.add_runtime_dependency 'pastel', '~> 0.7'
49
55
  spec.add_runtime_dependency 'tty-pie', '~> 0.3'
50
56
 
51
57
 
52
58
  spec.add_development_dependency 'coveralls', '~> 0.8'
53
- spec.add_development_dependency 'cucumber', '~> 3.0'
59
+ spec.add_development_dependency 'cucumber', '~> 5.1'
54
60
  spec.add_development_dependency 'pry-byebug', '~> 3.0'
55
61
  spec.add_development_dependency 'rake', '~> 13.0'
56
62
  spec.add_development_dependency 'rerun', '~> 0.13'
57
63
  spec.add_development_dependency 'rspec-expectations', '~> 3.0'
58
- spec.add_development_dependency 'rubocop', '~> 0.85.1'
64
+ spec.add_development_dependency 'rubocop', '~> 0.90.0'
59
65
  spec.add_development_dependency 'rspec', '~> 3.8'
60
66
 
67
+ spec.required_ruby_version = '~> 2.6'
61
68
  end
@@ -12,6 +12,8 @@ BackgroundRequiresMultipleScenarios:
12
12
  Enabled: true
13
13
  BadScenarioName:
14
14
  Enabled: true
15
+ EmptyFeatureFile:
16
+ Enabled: true
15
17
  FileNameDiffersFeatureName:
16
18
  Enabled: true
17
19
  GivensAfterBackground:
@@ -0,0 +1 @@
1
+ default: --publish-quiet
@@ -18,6 +18,9 @@ Chutney enforces its rules with the linters. These are:
18
18
  [AvoidScripting](https://github.com/BillyRuffian/chutney/blob/master/features/avoid_scripting.feature)
19
19
  : You have a lot of steps, are you sure you're not scripting the scenario when you should be specifying the behaviour of the system?
20
20
 
21
+ [AvoidTypographersQuotes](https://github.com/BillyRuffian/chutney/blob/master/features/avoid_typographers_quotes.feature)
22
+ : Cutting and pasting from Word documents? Is that pasting in curly-quotes instead of neutral ones you would type on a keyboard? Are you sure that's what you want?
23
+
21
24
  [BackgroundDoesMoreThanSetup](https://github.com/BillyRuffian/chutney/blob/master/features/background_does_more_than_setup.feature)
22
25
  : Background in feature files should only do setup activity and so they should only contain `Given` steps.
23
26
 
@@ -27,6 +30,9 @@ Chutney enforces its rules with the linters. These are:
27
30
  [BadScenarioName](https://github.com/BillyRuffian/chutney/blob/master/features/bad_scenario_name.feature)
28
31
  : You should avoid using words like 'test' or 'check' in your scenario names, instead you should define the behaviour of your system.
29
32
 
33
+ [EmptyFeatureFile](https://github.com/BillyRuffian/chutney/blob/master/features/empty_feature_file.feature)
34
+ : The feature should have content and should avoid committing empty features to repositories.
35
+
30
36
  [FileNameDiffersFeatureName](https://github.com/BillyRuffian/chutney/blob/master/features/file_name_differs_feature_name.feature)
31
37
  : The feature should have a name that follows the file name.
32
38
 
@@ -76,7 +82,7 @@ Chutney enforces its rules with the linters. These are:
76
82
  : This is a very long step. Consider writing it more concisely.
77
83
 
78
84
  [TooManyDifferentTags](https://github.com/BillyRuffian/chutney/blob/master/features/too_many_different_tags.feature)
79
- : This feature has a lot of differnt tags.
85
+ : This feature has a lot of different tags.
80
86
 
81
87
  [TooManySteps](https://github.com/BillyRuffian/chutney/blob/master/features/too_many_steps.feature)
82
88
  : This feature has a lot of steps. Consider writing it more concisely.
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'chutney'
3
5
  require 'chutney/formatter'
4
6
  require 'chutney/formatter/json_formatter'
@@ -10,19 +12,37 @@ formatters = Set.new
10
12
 
11
13
  OptionParser.new do |opts|
12
14
  opts.banner = 'Usage: chutney [files]'
13
- opts.on('-f',
14
- '--format [formatter]',
15
+ opts.on('-f',
16
+ '--format [formatter]',
15
17
  'One of JSONFormatter, PieFormatter or RainbowFormatter (default).') do |formatter|
16
18
  raise 'No Such Formatter' unless %w[JSONFormatter PieFormatter RainbowFormatter].include? formatter
17
19
 
18
20
  formatters << formatter
19
21
  end
22
+
23
+ opts.on('-l',
24
+ '--linters',
25
+ 'List the linter status by this configuration and exit') do
26
+ pastel = Pastel.new
27
+ chutney_config = Chutney::ChutneyLint.new.configuration
28
+ max_name_length = chutney_config.keys.map(&:length).max + 1
29
+ chutney_config.each do |linter, value|
30
+ print pastel.cyan(linter.ljust(max_name_length))
31
+
32
+ if value['Enabled']
33
+ puts pastel.green('enabled')
34
+ else
35
+ puts pastel.red('disabled')
36
+ end
37
+ end
38
+ exit
39
+ end
20
40
  end.parse!
21
41
 
22
42
  formatters << 'RainbowFormatter' if formatters.empty?
23
43
 
24
44
  files = ARGV.map { |pattern| Dir.glob(pattern) }.flatten
25
- files = Dir.glob('features/**/*.feature') if ARGV.empty?
45
+ files = Dir.glob('features/**/*.feature') if ARGV.empty?
26
46
 
27
47
  linter = Chutney::ChutneyLint.new(*files)
28
48
  report = linter.analyse
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amatch'
4
+
2
5
  require 'chutney/configuration'
3
6
  require 'chutney/linter'
4
7
  require 'chutney/linter/avoid_full_stop'
@@ -8,6 +11,7 @@ require 'chutney/linter/avoid_typographers_quotes'
8
11
  require 'chutney/linter/background_does_more_than_setup'
9
12
  require 'chutney/linter/background_requires_multiple_scenarios'
10
13
  require 'chutney/linter/bad_scenario_name'
14
+ require 'chutney/linter/empty_feature_file'
11
15
  require 'chutney/linter/file_name_differs_feature_name'
12
16
  require 'chutney/linter/givens_after_background'
13
17
  require 'chutney/linter/invalid_file_name'
@@ -32,9 +36,12 @@ require 'chutney/linter/unknown_variable'
32
36
  require 'chutney/linter/unused_variable'
33
37
  require 'chutney/linter/use_background'
34
38
  require 'chutney/linter/use_outline'
39
+ require 'chutney/version'
40
+
41
+ require 'cuke_modeler'
35
42
  require 'forwardable'
36
- require 'gherkin/dialect'
37
- require 'gherkin/parser'
43
+ # require 'gherkin/dialect'
44
+ # require 'gherkin/parser'
38
45
  require 'i18n'
39
46
  require 'set'
40
47
  require 'yaml'
@@ -44,33 +51,36 @@ module Chutney
44
51
  class ChutneyLint
45
52
  extend Forwardable
46
53
  attr_accessor :verbose
47
- attr_reader :files
48
- attr_reader :results
49
-
54
+ attr_reader :files, :results
55
+
50
56
  def_delegators :@files, :<<, :clear, :delete, :include?
51
57
 
52
58
  def initialize(*files)
53
59
  @files = files
54
60
  @results = Hash.new { |h, k| h[k] = [] }
55
61
  i18n_paths = Dir[File.expand_path(File.join(__dir__, 'config/locales')) + '/*.yml']
56
- return if I18n.load_path.include?(i18n_paths)
57
62
 
58
- I18n.load_path << i18n_paths
63
+ i18n_paths.each do |path|
64
+ next if I18n.load_path.include?(path)
65
+
66
+ I18n.load_path << path
67
+ I18n.backend.reload!
68
+ end
59
69
  end
60
-
70
+
61
71
  def configuration
62
72
  unless @config
63
- default_file = [File.expand_path('..', __dir__), '**/config', 'chutney.yml']
73
+ default_file = [File.expand_path('..', __dir__), '**/config', 'chutney_defaults.yml']
64
74
  config_file = Dir.glob(File.join(default_file)).first.freeze
65
75
  @config = Configuration.new(config_file)
66
76
  end
67
77
  @config
68
78
  end
69
-
79
+
70
80
  def configuration=(config)
71
81
  @config = config
72
82
  end
73
-
83
+
74
84
  def analyse
75
85
  files.each do |f|
76
86
  lint(f)
@@ -80,25 +90,19 @@ module Chutney
80
90
  # alias for non-british English
81
91
  # https://dictionary.cambridge.org/dictionary/english/analyse
82
92
  alias analyze analyse
83
-
93
+
84
94
  def linters
85
95
  @linters ||= Linter.descendants.filter { |l| configuration.dig(l.linter_name, 'Enabled') }
86
96
  end
87
-
97
+
88
98
  def linters=(*linters)
89
99
  @linters = linters
90
100
  end
91
-
101
+
92
102
  private
93
-
94
- def parse(text)
95
- @parser ||= Gherkin::Parser.new
96
- scanner = Gherkin::TokenScanner.new(text)
97
- @parser.parse(scanner)
98
- end
99
-
103
+
100
104
  def lint(file)
101
- parsed = parse(File.read(file))
105
+ parsed = CukeModeler::FeatureFile.new(file)
102
106
  linters.each do |linter_class|
103
107
  linter = linter_class.new(file, parsed, configuration[linter_class.linter_name])
104
108
  linter.lint
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
- module Chutney
4
+
5
+ module Chutney
3
6
  # gherkin_lint configuration object
4
7
  class Configuration < SimpleDelegator
5
8
  def initialize(path)
@@ -18,7 +21,11 @@ module Chutney
18
21
  end
19
22
 
20
23
  def load_user_configuration
21
- config_file = Dir.glob(File.join(Dir.pwd, '**', '.chutney.yml')).first
24
+ config_files = ['chutney.yml', '.chutney.yml'].map do |fname|
25
+ Dir.glob(File.join(Dir.pwd, '**', fname))
26
+ end.flatten
27
+
28
+ config_file = config_files.first
22
29
  merge_config(config_file) if !config_file.nil? && File.exist?(config_file)
23
30
  end
24
31
 
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # base class for all formatters
3
5
  class Formatter
4
6
  attr_accessor :results
5
-
7
+
6
8
  def initialize
7
9
  @results = {}
8
- end
9
-
10
+ end
11
+
10
12
  def files
11
13
  results.map { |k, _v| k }
12
14
  end
13
-
15
+
14
16
  def files_with_issues
15
17
  results.filter { |_k, v| v.any? { |r| r[:issues].count.positive? } }
16
18
  end
17
-
18
19
  end
19
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chutney
2
4
  # Plain old JSON formatter
3
5
  class JSONFormatter < Formatter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pastel'
2
4
  require 'tty/pie'
3
5
 
@@ -7,7 +9,7 @@ module Chutney
7
9
  def initialize
8
10
  super
9
11
  end
10
-
12
+
11
13
  def format
12
14
  data = top_offences.map do |offence|
13
15
  {
@@ -19,14 +21,14 @@ module Chutney
19
21
  end
20
22
  print_report(data)
21
23
  end
22
-
24
+
23
25
  def print_report(data)
24
26
  return if data.empty?
25
27
 
26
28
  print TTY::Pie.new(data: data, radius: 8, legend: { format: '%<label>s %<name>s %<value>i' })
27
29
  puts
28
30
  end
29
-
31
+
30
32
  def top_offences
31
33
  offence = Hash.new(0)
32
34
  files_with_issues.each do |_file, linter|
@@ -36,9 +38,9 @@ module Chutney
36
38
  end
37
39
  offence.reject { |_k, v| v.zero? }.sort_by { |_linter, count| -count }
38
40
  end
39
-
41
+
40
42
  def char_loop
41
- @char_looper ||= Fiber.new do
43
+ @char_looper ||= Fiber.new do
42
44
  chars = %w[• x + @ * / -]
43
45
  current = 0
44
46
  loop do
@@ -49,10 +51,10 @@ module Chutney
49
51
  end
50
52
  @char_looper.resume
51
53
  end
52
-
54
+
53
55
  def colour_loop
54
- @colour_looper ||= Fiber.new do
55
- colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
56
+ @colour_looper ||= Fiber.new do
57
+ colours = %i[bright_cyan bright_magenta bright_yellow bright_green]
56
58
  current = 0
57
59
  loop do
58
60
  current = 0 if current >= colours.count
@@ -62,7 +64,7 @@ module Chutney
62
64
  end
63
65
  @colour_looper.resume
64
66
  end
65
-
67
+
66
68
  def put_summary
67
69
  pastel = Pastel.new
68
70
  print "#{files.count} features inspected, "
@@ -72,6 +74,5 @@ module Chutney
72
74
  puts pastel.red("#{files_with_issues.count} taste nasty")
73
75
  end
74
76
  end
75
-
76
77
  end
77
78
  end
@@ -1,39 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pastel'
2
4
 
3
5
  module Chutney
4
6
  # pretty formatter
5
7
  class RainbowFormatter < Formatter
6
-
7
8
  def initialize
8
9
  super
9
-
10
+
10
11
  @pastel = Pastel.new
11
12
  end
12
-
13
- def format
13
+
14
+ def format
14
15
  files_with_issues.each do |file, linter|
15
16
  put_file(file)
16
17
  linter.filter { |l| !l[:issues].empty? }.each do |linter_with_issues|
17
-
18
18
  put_linter(linter_with_issues)
19
- linter_with_issues[:issues].each { |i| put_issue(i) }
19
+ linter_with_issues[:issues].each { |i| put_issue(file, i) }
20
20
  end
21
21
  end
22
22
  put_summary
23
23
  end
24
-
24
+
25
25
  def put_file(file)
26
26
  puts @pastel.cyan(file.to_s)
27
27
  end
28
-
28
+
29
29
  def put_linter(linter)
30
30
  puts @pastel.red(" #{linter[:linter]}")
31
31
  end
32
-
33
- def put_issue(issue)
34
- puts " #{@pastel.dim(issue.dig(:location, :line))} #{issue[:message]}"
32
+
33
+ def put_issue(file, issue)
34
+ puts " #{issue[:message]}"
35
+ puts " #{@pastel.dim file.to_s}:#{@pastel.dim(issue.dig(:location, :line))}"
35
36
  end
36
-
37
+
37
38
  def put_summary
38
39
  print "#{files.count} features inspected, "
39
40
  if files_with_issues.count.zero?
@@ -42,6 +43,5 @@ module Chutney
42
43
  puts @pastel.red("#{files_with_issues.count} taste nasty")
43
44
  end
44
45
  end
45
-
46
46
  end
47
47
  end