Skip to content

Commit

Permalink
Implement Formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
Shujon Mollah committed Jun 30, 2015
1 parent 289e4b2 commit e666625
Show file tree
Hide file tree
Showing 22 changed files with 351 additions and 137 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
greener
Greener
===

[![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)

A Gherkin .feature file linter
Keep your Cucumber and Spinach feature files greener :four_leaf_clover: with this configurable linter

**NOTE: This project is still under early-stage development**

Expand Down
14 changes: 9 additions & 5 deletions config/defaults.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# AllCheckers:
# Include:
# - "**/*.feature"
# Exclude:
# - "**/*_exclusionary_special.feature"
AllCheckers:
Formatters:
# Summary does not need to be added here explicitly
- Progress
- SimpleText
Include:
- "**/*.feature"
# Exclude:
# - "some/excluded/path/*.feature"

Style/FeatureName:
Enabled: true
Expand Down
18 changes: 16 additions & 2 deletions features/checker_feature_name.feature
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Feature: (checker) feature name
When I run `greener`
Then the output should contain:
"""
F
foo/mismatched_feature_title.feature:1
Feature: mismatched feature title LOL
^^^ feature title does not match file name
Expand All @@ -21,7 +23,12 @@ Feature: (checker) feature name
Feature: Valid Title
"""
When I run `greener`
Then the output should contain exactly "1 file(s) inspected, no offenses detected\n"
Then the output should contain:
"""
.
1 file(s) inspected, no offenses detected
"""

Scenario: punctuation allowed
Given a file named "foo/some_punctuation.feature" with:
Expand All @@ -35,7 +42,12 @@ Feature: (checker) feature name
AllowPunctuation: true
"""
When I run `greener --config config/punctuation_allowed.yml`
Then the output should contain exactly "1 file(s) inspected, no offenses detected\n"
Then the output should contain:
"""
.
1 file(s) inspected, no offenses detected
"""

Scenario: title case enforced
Given a file named "foo/this_isnt_title_case_yo.feature" with:
Expand All @@ -52,6 +64,8 @@ Feature: (checker) feature name
When I run `greener --config config/enforce_title_case.yml`
Then the output should contain:
"""
F
foo/this_isnt_title_case_yo.feature:1
Feature: this isn't Title Case, yo!
^^^ feature title is not title case. expected: This Isn't Title Case, Yo!
Expand Down
2 changes: 2 additions & 0 deletions features/checker_indentation_width.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Feature: (checker) indentation width
When I run `greener`
Then the output should contain:
"""
F
foo/indentation.feature:5
Scenario: poorly indented
^^^ inconsistent indentation detected
Expand Down
14 changes: 12 additions & 2 deletions features/configuration.feature
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ Feature: configuration
Enabled: false
"""
When I run `greener --config config/disabled.yml`
Then the output should contain exactly "1 file(s) inspected, no offenses detected\n"
Then the output should contain:
"""
.
1 file(s) inspected, no offenses detected
"""

Scenario: invalid checker specified in config
Given a file named "foo/something.feature" with:
Expand Down Expand Up @@ -46,4 +51,9 @@ Feature: configuration
Width: 4
"""
When I run `greener --config config/complex.yml`
Then the output should contain exactly "1 file(s) inspected, no offenses detected\n"
Then the output should contain:
"""
.
1 file(s) inspected, no offenses detected
"""
34 changes: 34 additions & 0 deletions features/formatter_progress.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Feature: (formatter) progress

Scenario: happy path
Given a file named "foo/good.feature" with:
"""
Feature: good
Scenario: correctly indented
Scenario: correctly indented
Then boom
"""
Given a file named "foo/good_too.feature" with:
"""
Feature: good too
Scenario: correctly indented
Scenario: correctly indented
Then boom
"""
And a file named "config/good.yml" with:
"""
AllCheckers:
Formatters:
- Progress
"""
When I run `greener --config config/good.yml`
Then the output should contain:
"""
..
2 file(s) inspected, no offenses detected
"""
2 changes: 2 additions & 0 deletions features/lint.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Feature: lint
When I run `greener`
Then the output should contain:
"""
F
foo/multiple_issues.feature:1
Feature: multiple issues
^^^ inconsistent indentation detected
Expand Down
1 change: 0 additions & 1 deletion lib/greener/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class CLI < Thor
def lint
linter = Linter.new(options[:config])
linter.lint
linter.print_results
end

default_task :lint
Expand Down
74 changes: 52 additions & 22 deletions lib/greener/config_store.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
require "yaml"

require "greener/utils"
require "greener/custom_error"
require "greener/error"

# Require all checker files here
require "greener/checker/style/feature_name"
require "greener/checker/style/indentation_width"

# And formatters
require "greener/formatter/progress"
require "greener/formatter/simple_text"
require "greener/formatter/summary"

module Greener
# Read configs from a user-specified greener.yml or fallback to defaults
class ConfigStore
include Utils

attr_reader :checkers, :files
attr_reader :checkers, :files, :formatters

def initialize(path, default_path = nil)
@path = path
Expand All @@ -21,16 +26,18 @@ def initialize(path, default_path = nil)

@checkers = {}
@files = []
@formatters = []
end

def resolve
if @path
fail CustomError, "No config file found at specified path: #{@path}" unless File.exist? @path
fail Error::Standard, "No config file found at specified path: #{@path}" unless File.exist? @path
config = load_yml_file @path
end
defaults = load_yml_file @default_path

@all = @path ? defaults.merge(config) : defaults
config ||= {}
defaults = load_yml_file @default_path
@all = merge_hashes(defaults, config)

validate
self
Expand All @@ -47,15 +54,47 @@ def files_matching_glob(glob)

private

# Deep merge, with a few post-merge checks
def merge_hashes(default, opt)
result = deep_merge(default, opt)
# Change nils to empty hashes/arrays so this class isn't littered with #nil? checks
result["AllCheckers"]["Exclude"] = [] if result["AllCheckers"]["Exclude"].nil?

result
end

def deep_merge(first, second)
merger = proc { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : v2 }
first.merge(second, &merger)
end

def validate
set_formatters
set_checkers
set_files

@all.delete("AllCheckers") if @all["AllCheckers"] && @all["AllCheckers"].empty?

@all.each do |k, _v|
fail CustomError, "Unknown option in config file: #{k}" # TODO: print warning instead of fail
fail Error::Standard, "Unknown option in config file: #{k}" # TODO: print warning instead of fail
end
end

def set_formatters
formatters = @all["AllCheckers"]["Formatters"].uniq.compact
# Ensure "Summary" formatter is in last position
if formatters.include?("Summary")
formatters << formatters.delete("Summary")
else
formatters << "Summary"
end
formatters.each do |f_string|
@formatters << formatter_from_string(f_string)
end

@all["AllCheckers"].delete "Formatters"
end

def set_checkers
@all.each do |k, v|
next unless %w( Style/ Lint/ ).any? { |prefix| k.start_with?(prefix) }
Expand All @@ -66,25 +105,16 @@ def set_checkers
end

def set_files
if @all["AllCheckers"].nil?
# Default to all .feature files recursively
return @files = files_matching_glob("**/*.feature")
end

@all.each do |k, v|
next unless k == "AllCheckers"
discover_files(v)
@all.delete(k)
end
end

def discover_files(hash)
includes = []
excludes = []

hash["Include"].each { |glob| includes += files_matching_glob(glob) } if hash["Include"]
hash["Exclude"].each { |glob| excludes += files_matching_glob(glob) } if hash["Exclude"]
@files = (includes - excludes).uniq
@all["AllCheckers"]["Include"].each { |glob| includes += files_matching_glob(glob) }
@all["AllCheckers"].delete "Include"

@all["AllCheckers"]["Exclude"].each { |glob| excludes += files_matching_glob(glob) }
@all["AllCheckers"].delete "Exclude"

@files = includes.uniq - excludes.uniq
end

def default_absolute_path
Expand Down
7 changes: 0 additions & 7 deletions lib/greener/custom_error.rb

This file was deleted.

7 changes: 7 additions & 0 deletions lib/greener/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Greener
module Error
class Standard < StandardError; end

class LintFailed < Standard; end
end
end
35 changes: 35 additions & 0 deletions lib/greener/formatter/base_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Greener
module Formatter
# Abstract base class for formatter, implements all public API methods.
#
# ## Method Invocation Order
#
# For example, when Greener inspects 2 files,
# the invocation order should be like this:
#
# * `#initialize`
# * `#started`
# * `#file_started`
# * `#file_finished`
# * `#file_started`
# * `#file_finished`
# * `#finished`
class BaseFormatter
def initialize(files)
@files = files
end

def started
end

def file_started
end

def file_finished(_violations)
end

def finished(_violations)
end
end
end
end
21 changes: 21 additions & 0 deletions lib/greener/formatter/progress.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "greener/formatter/base_formatter"

module Greener
module Formatter
# Print progress in real-time
class Progress < BaseFormatter
def file_finished(violations)
if violations.empty?
print "."
else
print "F"
end
end

def finished(_violations)
puts ""
puts ""
end
end
end
end
17 changes: 17 additions & 0 deletions lib/greener/formatter/simple_text.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require "greener/formatter/base_formatter"

module Greener
module Formatter
# Prints violation info that includes file, line number, text of the line, and message
class SimpleText < BaseFormatter
def finished(violations)
violations.each do |violation|
puts "#{violation[:file]}:#{violation[:line]}"
puts "#{violation[:text_of_line]}"
puts "#{' ' * (violation[:column] - 1)}^^^ #{violation[:message]}"
puts ""
end
end
end
end
end

0 comments on commit e666625

Please sign in to comment.