greener2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 21659a58c5605652857c5e13e450b645e5be355e
4
+ data.tar.gz: fb62aaca67ca27327e2dd2cb8ccf34dc65063540
5
+ SHA512:
6
+ metadata.gz: 75c81d38126470eae8e0dd52ca3577c3e42c4a01f67d414d948577b074b269855dc61b4b1645792e45f624c7cfa8f7ce01fa94b84269208a516d5c16ca9ed24a
7
+ data.tar.gz: 270a125694816a97cd10549d7b034d29abe763fbe605c84a65676174065adcbb9784b9b406f45bb672d1b808a49fe5325e752fc454db5c271314458b9c96b336
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 0.1.0 (June 29, 2015)
2
+
3
+ Features:
4
+
5
+ - First minor version release
6
+ - 2 checkers are available, `Style/FeatureName` and `Style/IndentationWidth`
7
+ - By default, `greener` checks for .feature files recursively from the current working directory. This can be overridden via the `AllCheckers:` > `Include:` or `Exclude:` keys in the yml.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 smoll
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ Greener
2
+ ===
3
+
4
+ [![Circle CI](https://circleci.com/gh/smoll/greener.svg?style=svg)](https://circleci.com/gh/smoll/greener) [![Code Climate](https://codeclimate.com/github/smoll/greener/badges/gpa.svg)](https://codeclimate.com/github/smoll/greener) [![Coverage Status](https://coveralls.io/repos/smoll/greener/badge.svg?branch=master)](https://coveralls.io/r/smoll/greener?branch=master) [![Gem Version](https://badge.fury.io/rb/greener.svg)](http://badge.fury.io/rb/greener) [![Inline docs](http://inch-ci.org/github/smoll/greener.svg?branch=master)](http://inch-ci.org/github/smoll/greener)
5
+
6
+ Keep your Cucumber and Spinach feature files greener :four_leaf_clover: with this configurable linter
7
+
8
+ **NOTE: This project is still under early-stage development**
9
+
10
+ ## TODOs
11
+
12
+ 0. Move "results printing" logic to configurable formatters
13
+ 0. Implement some kind of hooks system for use by formatters, similar to [this](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/formatter/base_formatter.rb#L30-L41)
14
+ 0. Create a RakeTask class for use in CI systems
15
+ 0. Add coloring for formatters
16
+
17
+ ## Usage
18
+
19
+ Install the gem
20
+ ```
21
+ gem install greener
22
+ ```
23
+
24
+ The `greener` binary takes a single argument, `-c path/to/config/greener.yml`. See the [defaults](./config/defaults.yml) for an example of this file.
25
+
26
+ View the [changelog](./CHANGELOG.md) for recent changes.
27
+
28
+ ## Contributing
29
+
30
+ Install dev dependencies locally
31
+ ```
32
+ bundle install
33
+ ```
34
+
35
+ Run tests
36
+ ```
37
+ bundle exec rake test
38
+ ```
39
+
40
+ View code coverage
41
+ ```
42
+ rake && open coverage/index.html
43
+ ```
44
+
45
+ ## Testing
46
+
47
+ To test the `greener` binary locally, `cd` to the repo root and run
48
+ ```
49
+ RUBYLIB=lib bundle exec ruby bin/greener
50
+ ```
51
+ Based on [this answer](http://stackoverflow.com/a/23367196/3456726).
52
+
53
+ ## References
54
+
55
+ #### CLI related
56
+
57
+ 0. http://guides.rubygems.org/make-your-own-gem/
58
+ 0. http://whatisthor.com/
59
+ 0. https://github.com/erikhuda/thor/wiki/Integrating-with-Aruba-In-Process-Runs
60
+
61
+ #### Testing related
62
+ 0. http://stackoverflow.com/questions/12673485/how-to-test-stdin-for-a-cli-using-rspec
63
+ 0. https://github.com/livinginthepast/aruba-rspec
64
+
65
+ #### Lint related
66
+
67
+ 0. https://github.com/bbatsov/rubocop/tree/master/lib/rubocop
data/bin/greener ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "greener/runner"
4
+
5
+ Greener::Runner.new(ARGV.dup).execute!
@@ -0,0 +1,20 @@
1
+ AllCheckers:
2
+ Formatters:
3
+ # Summary does not need to be added here explicitly
4
+ - Progress
5
+ - SimpleText
6
+ Include:
7
+ - "**/*.feature"
8
+ # Exclude:
9
+ # - "some/excluded/path/*.feature"
10
+
11
+ Style/FeatureName:
12
+ Enabled: true
13
+ # Allow non-alphanumeric characters, e.g. filename test_thing.feature with text "Feature: (test) thing"
14
+ AllowPunctuation: true
15
+ EnforceTitleCase: false
16
+
17
+ Style/IndentationWidth:
18
+ Enabled: true
19
+ # Number of spaces for each indentation level.
20
+ Width: 2
@@ -0,0 +1,4 @@
1
+ # Used by dogfood test, i.e. run greener against the .feature files in this repo!
2
+ AllCheckers:
3
+ Include:
4
+ - "features/**/*.feature"
@@ -0,0 +1,74 @@
1
+ Feature: (checker) feature name
2
+
3
+ Scenario: feature text does not match filename
4
+ Given a file named "foo/mismatched_feature_title.feature" with:
5
+ """
6
+ Feature: mismatched feature title LOL
7
+ """
8
+ When I run `greener`
9
+ Then the output should contain:
10
+ """
11
+ F
12
+
13
+ foo/mismatched_feature_title.feature:1
14
+ Feature: mismatched feature title LOL
15
+ ^^^ feature title does not match file name
16
+
17
+ 1 file(s) inspected, 1 offense(s) detected
18
+ """
19
+
20
+ Scenario: capitalization allowed
21
+ Given a file named "foo/valid_title.feature" with:
22
+ """
23
+ Feature: Valid Title
24
+ """
25
+ When I run `greener`
26
+ Then the output should contain:
27
+ """
28
+ .
29
+
30
+ 1 file(s) inspected, no offenses detected
31
+ """
32
+
33
+ Scenario: punctuation allowed
34
+ Given a file named "foo/some_punctuation.feature" with:
35
+ """
36
+ Feature: (some) punctuation
37
+ """
38
+ And a file named "config/punctuation_allowed.yml" with:
39
+ """
40
+ Style/FeatureName:
41
+ Enabled: true
42
+ AllowPunctuation: true
43
+ """
44
+ When I run `greener --config config/punctuation_allowed.yml`
45
+ Then the output should contain:
46
+ """
47
+ .
48
+
49
+ 1 file(s) inspected, no offenses detected
50
+ """
51
+
52
+ Scenario: title case enforced
53
+ Given a file named "foo/this_isnt_title_case_yo.feature" with:
54
+ """
55
+ Feature: this isn't Title Case, yo!
56
+ """
57
+ And a file named "config/enforce_title_case.yml" with:
58
+ """
59
+ Style/FeatureName:
60
+ Enabled: true
61
+ AllowPunctuation: true
62
+ EnforceTitleCase: true
63
+ """
64
+ When I run `greener --config config/enforce_title_case.yml`
65
+ Then the output should contain:
66
+ """
67
+ F
68
+
69
+ foo/this_isnt_title_case_yo.feature:1
70
+ Feature: this isn't Title Case, yo!
71
+ ^^^ feature title is not title case. expected: This Isn't Title Case, Yo!
72
+
73
+ 1 file(s) inspected, 1 offense(s) detected
74
+ """
@@ -0,0 +1,23 @@
1
+ Feature: (checker) indentation width
2
+
3
+ Scenario: inconsistent indentation
4
+ Given a file named "foo/indentation.feature" with:
5
+ """
6
+ Feature: indentation
7
+
8
+ Scenario: correctly indented
9
+
10
+ Scenario: poorly indented
11
+ Then nothing
12
+ """
13
+ When I run `greener`
14
+ Then the output should contain:
15
+ """
16
+ F
17
+
18
+ foo/indentation.feature:5
19
+ Scenario: poorly indented
20
+ ^^^ inconsistent indentation detected
21
+
22
+ 1 file(s) inspected, 1 offense(s) detected
23
+ """
@@ -0,0 +1,59 @@
1
+ Feature: configuration
2
+
3
+ Scenario: disable checker via config file
4
+ Given a file named "foo/mismatched_feature_title.feature" with:
5
+ """
6
+ Feature: mismatched feature title LOL
7
+ """
8
+ And a file named "config/disabled.yml" with:
9
+ """
10
+ Style/FeatureName:
11
+ Enabled: false
12
+ """
13
+ When I run `greener --config config/disabled.yml`
14
+ Then the output should contain:
15
+ """
16
+ .
17
+
18
+ 1 file(s) inspected, no offenses detected
19
+ """
20
+
21
+ Scenario: invalid checker specified in config
22
+ Given a file named "foo/something.feature" with:
23
+ """
24
+ Feature: something
25
+ """
26
+ And a file named "config/invalid.yml" with:
27
+ """
28
+ Style/NotEvenReal:
29
+ Enabled: false
30
+ """
31
+ When I run `greener --config config/invalid.yml`
32
+ Then the output should contain exactly "Unknown checker specified: Style/NotEvenReal\n"
33
+
34
+ Scenario: complex configuration
35
+ Given a file named "foo/indentation_too.feature" with:
36
+ """
37
+ Feature: BAD NAME LOL
38
+
39
+ Scenario: correctly indented
40
+
41
+ Scenario: correctly indented
42
+ Then boom
43
+ """
44
+ And a file named "config/complex.yml" with:
45
+ """
46
+ Style/FeatureName:
47
+ Enabled: false
48
+
49
+ Style/IndentationWidth:
50
+ Enabled: true
51
+ Width: 4
52
+ """
53
+ When I run `greener --config config/complex.yml`
54
+ Then the output should contain:
55
+ """
56
+ .
57
+
58
+ 1 file(s) inspected, no offenses detected
59
+ """
@@ -0,0 +1,34 @@
1
+ Feature: (formatter) progress
2
+
3
+ Scenario: happy path
4
+ Given a file named "foo/good.feature" with:
5
+ """
6
+ Feature: good
7
+
8
+ Scenario: correctly indented
9
+
10
+ Scenario: correctly indented
11
+ Then boom
12
+ """
13
+ Given a file named "foo/good_too.feature" with:
14
+ """
15
+ Feature: good too
16
+
17
+ Scenario: correctly indented
18
+
19
+ Scenario: correctly indented
20
+ Then boom
21
+ """
22
+ And a file named "config/good.yml" with:
23
+ """
24
+ AllCheckers:
25
+ Formatters:
26
+ - Progress
27
+ """
28
+ When I run `greener --config config/good.yml`
29
+ Then the output should contain:
30
+ """
31
+ ..
32
+
33
+ 2 file(s) inspected, no offenses detected
34
+ """
@@ -0,0 +1,27 @@
1
+ Feature: lint
2
+
3
+ Scenario: multiple issues
4
+ Given a file named "foo/multiple_issues.feature" with:
5
+ """
6
+ Feature: multiple issues
7
+
8
+ Scenario: correctly indented
9
+
10
+ Scenario: poorly indented
11
+ Then nothing
12
+ """
13
+ When I run `greener`
14
+ Then the output should contain:
15
+ """
16
+ F
17
+
18
+ foo/multiple_issues.feature:1
19
+ Feature: multiple issues
20
+ ^^^ inconsistent indentation detected
21
+
22
+ foo/multiple_issues.feature:5
23
+ Scenario: poorly indented
24
+ ^^^ inconsistent indentation detected
25
+
26
+ 1 file(s) inspected, 2 offense(s) detected\n
27
+ """
@@ -0,0 +1,9 @@
1
+ require "simplecov"
2
+ require "aruba/cucumber"
3
+ require "aruba/in_process"
4
+ require "greener/runner"
5
+
6
+ SimpleCov.command_name "features"
7
+
8
+ Aruba::InProcess.main_class = Greener::Runner
9
+ Aruba.process = Aruba::InProcess
data/greener.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "greener/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "greener2"
8
+ spec.version = Greener::VERSION
9
+ spec.authors = ["ikozakov"]
10
+ spec.email = ["ivankozakov0@gmail.com"]
11
+
12
+ spec.summary = "A Gherkin .feature file linter"
13
+ spec.description = "Keep your Gherkin readable with the greener linter..."
14
+ spec.homepage = "https://github.com/ikozakov/greener"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($RS).reject do |file|
18
+ file =~ %r{^(?:
19
+ spec/.*
20
+ |\.gitignore
21
+ |\.rspec
22
+ |\.rubocop.yml
23
+ |\.rubocop_todo.yml
24
+ |\.simplecov
25
+ |circle.yml
26
+ |cucumber.yml
27
+ |Gemfile
28
+ |Rakefile
29
+ )$}x
30
+ end
31
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ spec.add_dependency "gherkin3", "~> 3.1.1"
35
+ spec.add_dependency "thor", "~> 0.19.1"
36
+ spec.add_dependency "titleize", "~> 1.3.0"
37
+
38
+ spec.add_development_dependency "aruba", "~> 0.6.2"
39
+ spec.add_development_dependency "bundler", ">= 1.9.5"
40
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4.6"
41
+ spec.add_development_dependency "coveralls", "~> 0.7.9"
42
+ spec.add_development_dependency "rake", "~> 10.0"
43
+ spec.add_development_dependency "rspec", "~> 3.3.0"
44
+ spec.add_development_dependency "rubocop", "~> 0.32.0"
45
+ spec.add_development_dependency "simplecov", "~> 0.9.1"
46
+ end
@@ -0,0 +1,51 @@
1
+ module Greener
2
+ module Checker
3
+ # Base checker class
4
+ class Base
5
+ attr_reader :violations
6
+
7
+ def initialize(ast, path, config)
8
+ @ast = ast
9
+ @path = path
10
+ @config = config
11
+
12
+ @violations = []
13
+ end
14
+
15
+ # Method invoked when a checker is applied to a file
16
+ def run
17
+ end
18
+
19
+ # Read the violation message text set in the checker subclass
20
+ def message
21
+ self.class::MSG
22
+ end
23
+
24
+ # Adds violation data to the @violations array
25
+ def log_violation(line, col, msg = nil, raw_txt = nil)
26
+ # Set defaults for last 2 params if not overridden
27
+ raw_txt ||= raw_line(line)
28
+ msg ||= message
29
+
30
+ violation = {}
31
+ violation[:file] = @path
32
+ violation[:line] = line
33
+ violation[:column] = col
34
+ violation[:text_of_line] = raw_txt
35
+ violation[:message] = msg
36
+
37
+ @violations << violation
38
+ end
39
+
40
+ # For readability in checker subclasses
41
+ def feature
42
+ @ast
43
+ end
44
+
45
+ # Given a num, returns the full text corresponding to that line number in the file
46
+ def raw_line(num)
47
+ open(@path).each_line.take(num).last
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ require "greener/checker/base"
2
+ require "titleize"
3
+
4
+ module Greener
5
+ module Checker
6
+ module Style
7
+ # Ensure .feature filename matches feature name in the Gherkin
8
+ # e.g. filename_of_feature.feature => "Feature: filename of feature"
9
+ class FeatureName < Base
10
+ MSG = "feature title does not match file name"
11
+
12
+ def run
13
+ return unless @config["Enabled"]
14
+
15
+ run_against_filename
16
+ run_against_titlecase
17
+ end
18
+
19
+ def run_against_filename
20
+ filename_without_extension = File.basename(@path, ".*")
21
+ expected = feature[:name]
22
+ expected = expected.gsub(/[^0-9a-z ]/i, "") if allow_punctuation?
23
+ expected = expected.downcase.gsub(" ", "_")
24
+ return if filename_without_extension == expected
25
+ log_violation(feature[:location][:line], feature[:location][:column])
26
+ end
27
+
28
+ def run_against_titlecase
29
+ return unless enforce_title_case?
30
+ return if feature[:name].titlecase == feature[:name]
31
+ log_violation(
32
+ feature[:location][:line],
33
+ feature[:location][:column],
34
+ "feature title is not title case. expected: #{feature[:name].titlecase}"
35
+ )
36
+ end
37
+
38
+ def allow_punctuation?
39
+ @config["AllowPunctuation"]
40
+ end
41
+
42
+ def enforce_title_case?
43
+ @config["EnforceTitleCase"]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ require "greener/checker/base"
2
+
3
+ module Greener
4
+ module Checker
5
+ module Style
6
+ # Ensure keywords are indented correctly
7
+ # Ref: https://github.com/bbatsov/rubocop/commit/44c1cdd5d9bc1c3588ea7841fc6e9543126306e8
8
+ class IndentationWidth < Base
9
+ MSG = "inconsistent indentation detected"
10
+
11
+ def run
12
+ return unless @config["Enabled"]
13
+
14
+ check_feature_line
15
+ check_every_scenario
16
+ end
17
+
18
+ def check_feature_line
19
+ return if feature[:location][:column] == 1
20
+ log_violation(feature[:location][:line], feature[:location][:column])
21
+ end
22
+
23
+ def check_every_scenario
24
+ feature[:scenarioDefinitions].each do |scenario|
25
+ scenario[:steps].each { |step| check_a_step(step) }
26
+
27
+ next if scenario[:location][:column] == (1 + configured_indentation_width)
28
+ log_violation(scenario[:location][:line], scenario[:location][:column])
29
+ end
30
+ end
31
+
32
+ def check_a_step(step) # nil if a scenario has no steps, or
33
+ # {:type=>:Step, :location=>{:line=>6, :column=>5}, :keyword=>"Then ", :text=>"nothing"}
34
+ return if step[:location][:column] == (1 + configured_indentation_width * 2)
35
+ log_violation(step[:location][:line], step[:location][:column])
36
+ end
37
+
38
+ def configured_indentation_width
39
+ @config["Width"]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ require "thor"
2
+
3
+ require "greener/linter"
4
+
5
+ module Greener
6
+ # Main Thor application
7
+ # Subcommands map directly to instance methods in this class.
8
+ # e.g. entering `greener whatever` in your terminal should invoke the App#whatever method
9
+ class CLI < Thor
10
+ desc "lint", "The default task to run when no command is given"
11
+ method_options %w( config -c ) => :string
12
+ def lint
13
+ linter = Linter.new(options[:config])
14
+ linter.lint
15
+ end
16
+
17
+ default_task :lint
18
+ end
19
+ end
@@ -0,0 +1,124 @@
1
+ require "yaml"
2
+
3
+ require "greener/utils"
4
+ require "greener/error"
5
+
6
+ # Require all checker files here
7
+ require "greener/checker/style/feature_name"
8
+ require "greener/checker/style/indentation_width"
9
+
10
+ # And formatters
11
+ require "greener/formatter/progress"
12
+ require "greener/formatter/simple_text"
13
+ require "greener/formatter/summary"
14
+
15
+ module Greener
16
+ # Read configs from a user-specified greener.yml or fallback to defaults
17
+ class ConfigStore
18
+ include Utils
19
+
20
+ attr_reader :checkers, :files, :formatters
21
+
22
+ def initialize(path, default_path = nil)
23
+ @path = path
24
+ default_path ||= default_absolute_path
25
+ @default_path = default_path
26
+
27
+ @checkers = {}
28
+ @files = []
29
+ @formatters = []
30
+ end
31
+
32
+ def resolve
33
+ if @path
34
+ fail Error::Standard, "No config file found at specified path: #{@path}" unless File.exist? @path
35
+ config = load_yml_file @path
36
+ end
37
+
38
+ config ||= {}
39
+ defaults = load_yml_file @default_path
40
+ @all = merge_hashes(defaults, config)
41
+
42
+ validate
43
+ self
44
+ end
45
+
46
+ # Stub-able methods
47
+ def load_yml_file(path)
48
+ YAML.load_file(path)
49
+ end
50
+
51
+ def files_matching_glob(glob)
52
+ Dir.glob(glob).select { |e| File.file? e }
53
+ end
54
+
55
+ private
56
+
57
+ # Deep merge, with a few post-merge checks
58
+ def merge_hashes(default, opt)
59
+ result = deep_merge(default, opt)
60
+ # Change nils to empty hashes/arrays so this class isn't littered with #nil? checks
61
+ result["AllCheckers"]["Exclude"] = [] if result["AllCheckers"]["Exclude"].nil?
62
+
63
+ result
64
+ end
65
+
66
+ def deep_merge(first, second)
67
+ merger = proc { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : v2 }
68
+ first.merge(second, &merger)
69
+ end
70
+
71
+ def validate
72
+ set_formatters
73
+ set_checkers
74
+ set_files
75
+
76
+ @all.delete("AllCheckers") if @all["AllCheckers"] && @all["AllCheckers"].empty?
77
+
78
+ @all.each do |k, _v|
79
+ fail Error::Standard, "Unknown option in config file: #{k}" # TODO: print warning instead of fail
80
+ end
81
+ end
82
+
83
+ def set_formatters
84
+ formatters = @all["AllCheckers"]["Formatters"].uniq.compact
85
+ # Ensure "Summary" formatter is in last position
86
+ if formatters.include?("Summary")
87
+ formatters << formatters.delete("Summary")
88
+ else
89
+ formatters << "Summary"
90
+ end
91
+ formatters.each do |f_string|
92
+ @formatters << formatter_from_string(f_string)
93
+ end
94
+
95
+ @all["AllCheckers"].delete "Formatters"
96
+ end
97
+
98
+ def set_checkers
99
+ @all.each do |k, v|
100
+ next unless %w( Style/ Lint/ ).any? { |prefix| k.start_with?(prefix) }
101
+ checker_klass = checker_from_string(k)
102
+ @checkers[checker_klass] = v
103
+ @all.delete(k)
104
+ end
105
+ end
106
+
107
+ def set_files
108
+ includes = []
109
+ excludes = []
110
+
111
+ @all["AllCheckers"]["Include"].each { |glob| includes += files_matching_glob(glob) }
112
+ @all["AllCheckers"].delete "Include"
113
+
114
+ @all["AllCheckers"]["Exclude"].each { |glob| excludes += files_matching_glob(glob) }
115
+ @all["AllCheckers"].delete "Exclude"
116
+
117
+ @files = includes.uniq - excludes.uniq
118
+ end
119
+
120
+ def default_absolute_path
121
+ File.expand_path("../../../config/defaults.yml", __FILE__)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,7 @@
1
+ module Greener
2
+ module Error
3
+ class Standard < StandardError; end
4
+
5
+ class LintFailed < Standard; end
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ module Greener
2
+ module Formatter
3
+ # Abstract base class for formatter, implements all public API methods.
4
+ #
5
+ # ## Method Invocation Order
6
+ #
7
+ # For example, when Greener inspects 2 files,
8
+ # the invocation order should be like this:
9
+ #
10
+ # * `#initialize`
11
+ # * `#started`
12
+ # * `#file_started`
13
+ # * `#file_finished`
14
+ # * `#file_started`
15
+ # * `#file_finished`
16
+ # * `#finished`
17
+ class BaseFormatter
18
+ def initialize(files)
19
+ @files = files
20
+ end
21
+
22
+ def started
23
+ end
24
+
25
+ def file_started
26
+ end
27
+
28
+ def file_finished(_violations)
29
+ end
30
+
31
+ def finished(_violations)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ require "greener/formatter/base_formatter"
2
+
3
+ module Greener
4
+ module Formatter
5
+ # Print progress in real-time
6
+ class Progress < BaseFormatter
7
+ def file_finished(violations)
8
+ if violations.empty?
9
+ print "."
10
+ else
11
+ print "F"
12
+ end
13
+ end
14
+
15
+ def finished(_violations)
16
+ puts ""
17
+ puts ""
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ require "greener/formatter/base_formatter"
2
+
3
+ module Greener
4
+ module Formatter
5
+ # Prints violation info that includes file, line number, text of the line, and message
6
+ class SimpleText < BaseFormatter
7
+ def finished(violations)
8
+ violations.each do |violation|
9
+ puts "#{violation[:file]}:#{violation[:line]}"
10
+ puts "#{violation[:text_of_line]}"
11
+ puts "#{' ' * (violation[:column] - 1)}^^^ #{violation[:message]}"
12
+ puts ""
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require "greener/formatter/base_formatter"
2
+
3
+ module Greener
4
+ module Formatter
5
+ # Prints summary line, e.g. "10 file(s) inspected, no offenses detected"
6
+ class Summary < BaseFormatter
7
+ def finished(violations)
8
+ conclusion = violations.empty? ? "no offenses detected" : "#{violations.count} offense(s) detected"
9
+
10
+ res = "#{@files.count} file(s) inspected, #{conclusion}"
11
+ return puts(res) if violations.empty?
12
+ fail Greener::Error::LintFailed, res
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Greener
2
+ # A set of Greener::Formatter::SomeFormatter objects
3
+ class FormatterSet
4
+ def initialize(formatters)
5
+ @formatters = formatters
6
+ end
7
+
8
+ # Note the "d"
9
+ def initialized(*args)
10
+ @formatters = @formatters.map { |formatter| formatter.new(*args) }
11
+ end
12
+
13
+ # All these instance methods have the same method signature as those of the Formatter classes
14
+ [:started, :file_started, :file_finished, :finished].each do |method_name|
15
+ define_method method_name do |*args|
16
+ @formatters.each { |formatter| formatter.send(method_name, *args) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ require "greener/config_store"
2
+ require "greener/formatter_set"
3
+ require "greener/parser"
4
+
5
+ module Greener
6
+ # Parse then lint a collection of .feature files for violations
7
+ class Linter
8
+ def initialize(config_path)
9
+ @config = ConfigStore.new(config_path).resolve
10
+ @results = []
11
+ @formatter_set = FormatterSet.new @config.formatters
12
+ end
13
+
14
+ def lint
15
+ @formatter_set.initialized(@config.files)
16
+ @formatter_set.started
17
+
18
+ # Here we iterate through a list of files, then iterate through the list of
19
+ # desired checkers, passing each one the filename & parsed AST.
20
+ # This is fine for now, but to speed this up we could refactor this to do a
21
+ # single pass through the file, line-by-line, and flagging violations as
22
+ # they are encountered.
23
+ @config.files.each do |f|
24
+ process_file(f) # TODO: move this to its own class
25
+ end
26
+
27
+ @formatter_set.finished @results
28
+ end
29
+
30
+ private
31
+
32
+ def process_file(fname)
33
+ @formatter_set.file_started
34
+
35
+ ast = Parser.new(fname).ast
36
+
37
+ violations_in_file = []
38
+
39
+ @config.checkers.each do |sc_klass, sc_opts|
40
+ checker = sc_klass.new(ast, fname, sc_opts)
41
+ checker.run
42
+ violations_in_file += checker.violations
43
+ @results += checker.violations
44
+ end
45
+
46
+ @formatter_set.file_finished(violations_in_file)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,35 @@
1
+ require "stringio"
2
+
3
+ module Greener
4
+ # Initialize this class to delay output, https://gist.github.com/macek/596007
5
+ class OutputBuffer
6
+ def initialize
7
+ @buffer = StringIO.new
8
+ activate
9
+ end
10
+
11
+ def activate
12
+ return if @activated
13
+ self.class.original_stdout = $stdout
14
+ $stdout = @buffer
15
+ @activated = true
16
+ end
17
+
18
+ def to_s
19
+ @buffer.rewind
20
+ @buffer.read
21
+ end
22
+
23
+ def stop
24
+ self.class.restore_default
25
+ end
26
+
27
+ class << self
28
+ attr_accessor :original_stdout
29
+
30
+ def restore_default
31
+ $stdout = original_stdout
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ require "gherkin3/parser"
2
+ require "gherkin3/token_scanner"
3
+
4
+ module Greener
5
+ # Wrapper around Gherkin3's Parser
6
+ class Parser
7
+ def initialize(feature)
8
+ @feature = feature # filepath or String
9
+ end
10
+
11
+ # Return an Abstract Syntax Tree from a feature file
12
+ def ast
13
+ parser = Gherkin3::Parser.new
14
+ scanner = Gherkin3::TokenScanner.new(@feature)
15
+ parser.parse(scanner)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ require "greener/cli"
2
+
3
+ begin # require any dev dependencies here
4
+ require "byebug"
5
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
6
+ end
7
+
8
+ module Greener
9
+ # Aruba In Process + Thor integration
10
+ # Based on https://github.com/erikhuda/thor/wiki/Integrating-with-Aruba-In-Process-Runs
11
+ class Runner
12
+ # Allow everything fun to be injected from the outside while defaulting to normal implementations.
13
+ def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
14
+ @argv = argv
15
+ @stdin = stdin
16
+ @stdout = stdout
17
+ @stderr = stderr
18
+ @kernel = kernel
19
+ end
20
+
21
+ def execute!
22
+ exit_code = begin
23
+ # Thor accesses these streams directly rather than letting them be injected, so we replace them...
24
+ $stderr = @stderr
25
+ $stdin = @stdin
26
+ $stdout = @stdout
27
+
28
+ # Run our normal Thor app the way we know and love.
29
+ Greener::CLI.start(@argv)
30
+
31
+ # Thor::Base#start does not have a return value, assume success if no exception is raised.
32
+ 0
33
+ rescue Greener::Error::Standard => e
34
+ @stderr.puts e.message
35
+ 1
36
+ rescue StandardError => e
37
+ # The ruby interpreter would pipe this to STDERR and exit 1 in the case of an unhandled exception
38
+ b = e.backtrace
39
+ @stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
40
+ @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
41
+ 1
42
+ rescue SystemExit => e
43
+ e.status
44
+ ensure
45
+ # TODO: reset your app here, free up resources, etc.
46
+ # Examples:
47
+ # MyApp.logger.flush
48
+ # MyApp.logger.close
49
+ # MyApp.logger = nil
50
+ #
51
+ # MyApp.reset_singleton_instance_variables
52
+
53
+ # ...then we put the streams back.
54
+ $stderr = STDERR
55
+ $stdin = STDIN
56
+ $stdout = STDOUT
57
+ end
58
+
59
+ # Proxy our exit code back to the injected kernel.
60
+ @kernel.exit(exit_code)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,31 @@
1
+ module Greener
2
+ # Useful shared utils
3
+ module Utils
4
+ def checker_from_string(str)
5
+ namespaced = str.gsub("/", "::")
6
+ constantize "Greener::Checker::#{namespaced}"
7
+ rescue NameError
8
+ raise Error::Standard, "Unknown checker specified: #{str}" # TODO: print warning instead of failing
9
+ end
10
+
11
+ def formatter_from_string(str)
12
+ constantize "Greener::Formatter::#{str}"
13
+ rescue NameError
14
+ raise Error::Standard, "Unknown formatter specified: #{str}" # TODO: print warning instead of failing
15
+ end
16
+
17
+ private
18
+
19
+ # Borrowed from Rails
20
+ def constantize(camel_cased_word)
21
+ names = camel_cased_word.split("::")
22
+ names.shift if names.empty? || names.first.empty?
23
+
24
+ constant = Object
25
+ names.each do |name|
26
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
27
+ end
28
+ constant
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ # Gem version
2
+ module Greener
3
+ VERSION = "0.0.1"
4
+ end
data/lib/greener.rb ADDED
@@ -0,0 +1 @@
1
+ require "greener/runner"
metadata ADDED
@@ -0,0 +1,230 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: greener2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ikozakov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gherkin3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.19.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.19.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: titleize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: aruba
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.6.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.9.5
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.9.5
83
+ - !ruby/object:Gem::Dependency
84
+ name: codeclimate-test-reporter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.4.6
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.4.6
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.7.9
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.7.9
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 3.3.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 3.3.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.32.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.32.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.9.1
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.9.1
167
+ description: Keep your Gherkin readable with the greener linter...
168
+ email:
169
+ - ivankozakov0@gmail.com
170
+ executables:
171
+ - greener
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - CHANGELOG.md
176
+ - LICENSE.txt
177
+ - README.md
178
+ - bin/greener
179
+ - config/defaults.yml
180
+ - config/dogfood.yml
181
+ - features/checker_feature_name.feature
182
+ - features/checker_indentation_width.feature
183
+ - features/configuration.feature
184
+ - features/formatter_progress.feature
185
+ - features/lint.feature
186
+ - features/support/env.rb
187
+ - greener.gemspec
188
+ - lib/greener.rb
189
+ - lib/greener/checker/base.rb
190
+ - lib/greener/checker/style/feature_name.rb
191
+ - lib/greener/checker/style/indentation_width.rb
192
+ - lib/greener/cli.rb
193
+ - lib/greener/config_store.rb
194
+ - lib/greener/error.rb
195
+ - lib/greener/formatter/base_formatter.rb
196
+ - lib/greener/formatter/progress.rb
197
+ - lib/greener/formatter/simple_text.rb
198
+ - lib/greener/formatter/summary.rb
199
+ - lib/greener/formatter_set.rb
200
+ - lib/greener/linter.rb
201
+ - lib/greener/output_buffer.rb
202
+ - lib/greener/parser.rb
203
+ - lib/greener/runner.rb
204
+ - lib/greener/utils.rb
205
+ - lib/greener/version.rb
206
+ homepage: https://github.com/ikozakov/greener
207
+ licenses:
208
+ - MIT
209
+ metadata: {}
210
+ post_install_message:
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: '0'
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubyforge_project:
226
+ rubygems_version: 2.6.11
227
+ signing_key:
228
+ specification_version: 4
229
+ summary: A Gherkin .feature file linter
230
+ test_files: []