greener2 0.0.1

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 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: []