hound-tools 0.0.4

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.hound.yml +6 -0
  4. data/.hound/defaults.yml +265 -0
  5. data/.hound/overrides.yml +19 -0
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +8 -0
  8. data/.rubocop_merged_for_hound.yml +41 -0
  9. data/.rubocop_todo.yml +11 -0
  10. data/.travis.yml +12 -0
  11. data/Gemfile +12 -0
  12. data/Guardfile +40 -0
  13. data/LICENSE.txt +22 -0
  14. data/README.md +98 -0
  15. data/Rakefile +22 -0
  16. data/bin/hound-tools +5 -0
  17. data/hound-tools.gemspec +26 -0
  18. data/lib/hound/tools.rb +7 -0
  19. data/lib/hound/tools/cli.rb +74 -0
  20. data/lib/hound/tools/hound_defaults.rb +30 -0
  21. data/lib/hound/tools/hound_overrides.rb +26 -0
  22. data/lib/hound/tools/hound_yml.rb +31 -0
  23. data/lib/hound/tools/merged_yml.rb +40 -0
  24. data/lib/hound/tools/rubocop_yml.rb +24 -0
  25. data/lib/hound/tools/runner.rb +71 -0
  26. data/lib/hound/tools/template.rb +38 -0
  27. data/lib/hound/tools/templates/_.hound.yml +6 -0
  28. data/lib/hound/tools/templates/_.hound/defaults.yml +265 -0
  29. data/lib/hound/tools/templates/_.hound/overrides.yml +19 -0
  30. data/lib/hound/tools/templates/_.rubocop.yml +8 -0
  31. data/lib/hound/tools/version.rb +5 -0
  32. data/spec/lib/hound/tools/cli_spec.rb +90 -0
  33. data/spec/lib/hound/tools/hound_defaults_spec.rb +52 -0
  34. data/spec/lib/hound/tools/hound_overrides_spec.rb +52 -0
  35. data/spec/lib/hound/tools/hound_yml_spec.rb +77 -0
  36. data/spec/lib/hound/tools/merged_yml_spec.rb +53 -0
  37. data/spec/lib/hound/tools/rubocop_yml_spec.rb +68 -0
  38. data/spec/lib/hound/tools/template_spec.rb +43 -0
  39. data/spec/spec_helper.rb +47 -0
  40. metadata +147 -0
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Cezary Baginski
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ [![Gem Version](https://badge.fury.io/rb/hound-tools.svg)](http://badge.fury.io/rb/hound-tools)
2
+ [![Build Status](https://travis-ci.org/e2/hound-tools.png?branch=master)](https://travis-ci.org/e2/hound-tools)
3
+
4
+
5
+ # Hound::Tools
6
+
7
+ Tools and configuration to locally simulate HoundCi checks
8
+
9
+ ## Initial Setup
10
+
11
+ In your Gemfile:
12
+
13
+ ```ruby
14
+ group :development do
15
+ gem 'hound-tools', '~> 0.0.3', require: false
16
+ end
17
+ ```
18
+
19
+ Create customizable RuboCop + Hound setup:
20
+
21
+ ```bash
22
+ $ bundle exec hound-tools init
23
+ ```
24
+
25
+ (NOTE: this runs `rubocop --auto-gen`, so it will disable all the offenses.)
26
+
27
+ ## Add new files to repository
28
+
29
+ Add the files to the repository:
30
+
31
+ ```bash
32
+ git add .hound # default Hound style guide and your overrides
33
+ git add .rubocop.yml # Rubocop-only config
34
+ git add .hound.yml # Hound general style-checking config
35
+ git add .rubocop_merged_for_hound.yml # auto-generated Hound-only config (without Hound defaults)
36
+ ```
37
+
38
+
39
+ ## Customizing your Rubocop/Hound setup
40
+
41
+ You should only be interested in these files:
42
+ - `.rubocop_todo.yml`, which can be updated with `bundle exec rubocop --auto-gen` and then edited
43
+ - `.hound/overrides.yml`, where you can override Hound's default rules or excludes
44
+ - `.rubocop_merged_for_hound.yml` - you want to regenerate this and commit
45
+
46
+ ## Usage
47
+
48
+ Every time you modify `.rubocop_todo.yml` or `.hound/overrides.yml`, you'll
49
+ want to regenerate `.rubocop_merged_for_hound.yml` with:
50
+
51
+ ```bash
52
+ $ bundle exec hound-tools
53
+ ```
54
+
55
+ (And then you'll want to add all 3 to the repository before doing anything else).
56
+
57
+ Once you have your style working, you can simulate Hound with almost 100% accuracy with:
58
+
59
+ ```bash
60
+ bundle exec rubocop
61
+ ```
62
+
63
+ (If this shows no offenses, check the .rubocop_todo.yml file for disabled rules.)
64
+
65
+ ## Tips
66
+
67
+ 1) For quickly fixing most offenses, uncomment the 'auto-correct' ones in
68
+ `.rubocop_todo.yml` simply run `bundle exec rubocop -a`
69
+
70
+ 2) The RuboCop README has tips on setting up Rake, Guard, etc.
71
+
72
+ ## Why is this so complex?
73
+
74
+ Well, simply because Hound doesn't support the RuboCop `inherited_from` keys.
75
+ And that's because Hound is avoiding touching the filesystem - which makes
76
+ sense since it downloads files to memory from GitHub.
77
+
78
+ Also, since Hound internally loads it's defaults and does a "mini-merge" of the
79
+ configurations, it needs a different setup than Rubocop.
80
+
81
+ I won't mention the clever hacks in Hound to side-step directory traversing and
82
+ other messy issues (e.g. handling excludes).
83
+
84
+
85
+ ## Alternatives
86
+
87
+ 1) Using only the default Hound settings (without being able to Hound-check them locally)
88
+
89
+ 2) Copying the default Hound settings (`.hound/defaults.yml`) to `.rubocop.yml` and tweak them (but you loose the flexibility and control of using multiple files and the coolness of .rubocop_todo.yml with 'inherited_from')
90
+
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it ( https://github.com/[my-github-username]/hound-tools/fork )
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create a new Pull Request
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ default_tasks = []
4
+
5
+ def ci?
6
+ ENV["CI"] == "true"
7
+ end
8
+
9
+ require "rspec/core/rake_task"
10
+ RSpec::Core::RakeTask.new(:spec) do |t|
11
+ t.verbose = ci?
12
+ end
13
+
14
+ default_tasks << :spec
15
+
16
+ unless ci?
17
+ require "rubocop/rake_task"
18
+ RuboCop::RakeTask.new(:rubocop)
19
+ default_tasks << :rubocop
20
+ end
21
+
22
+ task default: default_tasks
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hound/tools/cli'
4
+
5
+ Hound::Tools::Cli.start
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hound/tools/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'hound-tools'
8
+ spec.version = Hound::Tools::VERSION
9
+ spec.authors = ['Cezary Baginski']
10
+ spec.email = ['cezary@chronomantic.net']
11
+ spec.summary = 'Tools for configuring and using HoundCI'
12
+ spec.description = 'Matches your project config to give the same errors as HoundCi'
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'rubocop', '0.25.0'
22
+ spec.add_dependency 'thor'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ end
@@ -0,0 +1,7 @@
1
+ require 'hound/tools/version'
2
+
3
+ module Hound
4
+ module Tools
5
+ # Your code goes here...
6
+ end
7
+ end
@@ -0,0 +1,74 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+
4
+ require 'hound/tools/hound_yml'
5
+ require 'hound/tools/hound_defaults'
6
+ require 'hound/tools/hound_overrides'
7
+ require 'hound/tools/rubocop_yml'
8
+ require 'hound/tools/merged_yml'
9
+
10
+ require 'hound/tools/runner'
11
+
12
+ module Hound
13
+ module Tools
14
+ class Cli < Thor
15
+ INSTRUCTIONS = <<-EOS
16
+
17
+ **** WARNING!!! ****
18
+
19
+ 1. All Rubocop offenses are initially ignored! (see .rubocop_todo.yml and/or README)
20
+ - tweak .rubocop_todo.yml and regenerate with `bundle exec hound-tools`
21
+
22
+ 2. Fixing all offenses at once is discouraged unless:
23
+ - you are the only person actively working on the project (or starting out)
24
+ - you have accepted ALL the pull requests FIRST
25
+ - you have merged ALL the local and remote branches and you are NOT
26
+ currently maintaining multiple branches
27
+
28
+ Issues? Go here: https://github.com/e2/hound-tools/issues
29
+ EOS
30
+
31
+ desc :init, 'Initializes a project to match default HoundCi config'
32
+ def init
33
+ HoundYml.new.generate
34
+ HoundDefaults.new.generate
35
+ HoundOverrides.new.generate
36
+ RubocopYml.new.generate
37
+
38
+ # TODO: help setup Rakefile?
39
+
40
+ Kernel.system('bundle exec rubocop --auto-gen')
41
+
42
+ MergedYml.new.generate
43
+
44
+ $stdout.puts INSTRUCTIONS
45
+
46
+ unless Kernel.system("bundle show hound-tools > #{IO::NULL}")
47
+ $stderr.puts <<-EOS
48
+ Add hound-tools to your Gemfile like so:
49
+
50
+ gem 'hound-tools', require: false
51
+
52
+ EOS
53
+ end
54
+ end
55
+
56
+ default_task :check
57
+ desc :check, 'Simulates a HoundCi check locally'
58
+ def check
59
+ # TODO: add an "update" action?
60
+ # TODO: only merge if necessary (files outdated)
61
+ MergedYml.new.generate
62
+
63
+ options = {
64
+ hound_yml_file: '.hound.yml',
65
+ hound_ci_style_file: '.hound/defaults.yml',
66
+ debug: false,
67
+ glob_pattern: '**/*.rb'
68
+ }
69
+
70
+ Runner.new(options).run
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,30 @@
1
+ require 'yaml'
2
+
3
+ require 'hound/tools/template'
4
+
5
+ module Hound
6
+ module Tools
7
+ class HoundDefaults
8
+ include Template
9
+
10
+ def initialize
11
+ super('.hound/defaults.yml')
12
+ end
13
+
14
+ private
15
+
16
+ def _validate(content)
17
+ config = YAML.load(content)
18
+ literals = config['StringLiterals']
19
+ fail InvalidTemplate, 'No StringLiterals section' unless literals
20
+
21
+ quote_style = literals['EnforcedStyle']
22
+ fail InvalidTemplate, 'No EnforcedStyle section' unless quote_style
23
+ fail InvalidTemplate, 'No double_quotes value' unless quote_style == 'double_quotes'
24
+
25
+ # TODO: not tested
26
+ fail InvalidTemplate, "Detected 'inherited_from' section" if config.key?('inherited_from')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+
3
+ require 'hound/tools/template'
4
+
5
+ module Hound
6
+ module Tools
7
+ class HoundOverrides
8
+ include Template
9
+
10
+ def initialize
11
+ super('.hound/overrides.yml')
12
+ end
13
+
14
+ private
15
+
16
+ def _validate(content)
17
+ config = YAML.load(content)
18
+ cops = config['AllCops']
19
+ fail InvalidTemplate, 'No AllCops section' unless cops
20
+
21
+ # TODO: not tested
22
+ fail InvalidTemplate, "Detected 'inherited_from' section" if config.key?('inherited_from')
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ require 'yaml'
2
+
3
+ require 'hound/tools/template'
4
+
5
+ module Hound
6
+ module Tools
7
+ class HoundYml
8
+ include Template
9
+
10
+ def initialize
11
+ super('.hound.yml')
12
+ end
13
+
14
+ def rubocop_filename
15
+ data = IO.read(filename)
16
+ _validate(data)
17
+ YAML.load(data)['ruby']['config_file']
18
+ end
19
+
20
+ private
21
+
22
+ def _validate(data)
23
+ config = YAML.load(data)
24
+ ruby = config['ruby']
25
+ fail InvalidTemplate, "No 'ruby' section" unless ruby
26
+ fail InvalidTemplate, "Expected 'ruby' section to be a hash, got #{ruby.inspect}" unless ruby.is_a?(Hash)
27
+ fail InvalidTemplate, "No 'config_file' section" unless ruby.key?('config_file')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ require 'hound/tools/template'
2
+ require 'hound/tools/hound_yml'
3
+ require 'hound/tools/hound_overrides'
4
+
5
+ module Hound
6
+ module Tools
7
+ class MergedYml
8
+ include Template
9
+
10
+ def initialize
11
+ @todo_file = '.rubocop_todo.yml'
12
+
13
+ # NOTE: should be named. .rubocop.yml to prevent RuboCop from traversing
14
+ super('.rubocop_merged_for_hound.yml')
15
+ end
16
+
17
+ def generate
18
+ s = StringIO.new
19
+ s.write(<<EOS)
20
+ # This is a file generated by `hound-tools`
21
+ #
22
+ # We don't include .hound/defaults.yml, because Hound has internally
23
+ # loaded them at this point
24
+ #
25
+ EOS
26
+ [HoundOverrides.new.filename, @todo_file].each do |filename|
27
+ s.puts '# ---------------------------------'
28
+ s.puts "# #{filename}"
29
+ s.puts '# ---------------------------------'
30
+ s.puts IO.read(filename)
31
+ end
32
+ Pathname.new(filename).dirname.mkpath
33
+ output_file = HoundYml.new.rubocop_filename
34
+ IO.write(output_file, s.string)
35
+
36
+ $stdout.puts "#{output_file} (regenerated)"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ require 'yaml'
2
+ require 'hound/tools/template'
3
+
4
+ module Hound
5
+ module Tools
6
+ class RubocopYml
7
+ include Template
8
+
9
+ def initialize
10
+ super '.rubocop.yml'
11
+ end
12
+
13
+ private
14
+
15
+ def _validate(data)
16
+ config = YAML.load(data)
17
+ inherited = config['inherit_from']
18
+ fail InvalidTemplate, "No 'inherit_from' section" unless inherited
19
+ file = '.hound/defaults.yml'
20
+ fail InvalidTemplate, "'#{file}' not inherited" unless inherited.include?(file)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ module Hound
2
+ module Tools
3
+ class HoundConfig
4
+ attr_reader :yaml
5
+ attr_reader :rubocop_file
6
+ attr_reader :rubocop_data
7
+
8
+ def enabled?
9
+ @enabled
10
+ end
11
+
12
+ def initialize(filename)
13
+ @yaml = YAML.load(IO.read(filename))
14
+ @enabled = @yaml['ruby'].fetch('enabled', true)
15
+ @rubocop_file = @yaml['ruby'].fetch('config_file', nil)
16
+ @rubocop_data = @rubocop_file && YAML.load(IO.read(@rubocop_file))
17
+ end
18
+ end
19
+
20
+ class Runner
21
+ DEFAULTS = {
22
+ hound_yml_file: '.hound.yml',
23
+ hound_ci_style_file: '.hound/defaults.yml',
24
+ debug: false,
25
+ glob_pattern: '**/*.rb' # TODO: should be all files?
26
+ }
27
+
28
+ def initialize(options)
29
+ @options = DEFAULTS.merge(options)
30
+ end
31
+
32
+ def run
33
+ # TODO: clean this up
34
+ # TODO: not covered by specs
35
+
36
+ hound_yml_file = @options[:hound_yml_file]
37
+ hound_ci_style = @options[:hound_ci_style_file]
38
+ debug = @options[:debug]
39
+ glob_pattern = @options[:glob_pattern]
40
+
41
+ # NOTE: the code below should be written to EXACTLY do what Hound does
42
+
43
+ require 'rubocop'
44
+
45
+ RuboCop::ConfigLoader.debug = debug
46
+ hound = HoundConfig.new(hound_yml_file)
47
+
48
+ return "RuboCop disabled in #{hound_yml_file}" unless hound.enabled?
49
+
50
+ # NOTE: treating hound.yml as a rubocop.yml file is deprecated
51
+ custom = RuboCop::Config.new(hound.rubocop_data || hound.yml, '')
52
+ custom.add_missing_namespaces
53
+ custom.make_excludes_absolute
54
+
55
+ default = RuboCop::ConfigLoader.configuration_from_file(hound_ci_style)
56
+ config = RuboCop::ConfigLoader.merge(default, custom)
57
+
58
+ cfg = RuboCop::Config.new(config, '')
59
+ team = RuboCop::Cop::Team.new(RuboCop::Cop::Cop.all, cfg, debug: debug)
60
+
61
+ Dir[glob_pattern].each do |path|
62
+ next if File.directory?(path)
63
+ file = RuboCop::ProcessedSource.new(IO.read(path))
64
+ team.inspect_file(file).each do |violation|
65
+ $stderr.puts "#{path}:#{violation.line}:#{violation.message}"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end