code_hunter 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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in code_hunter.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ryo Nakamura
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.
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # Code Hunter
2
+ Hunt out weak spots in your rails application with 2 static metrics tools:
3
+
4
+ * [Brakeman](https://github.com/presidentbeef/brakeman) - A static analysis security vulnerability scanner for Rails
5
+ * [RailsBestPractices](https://github.com/railsbp/rails_best_practices) - A code metric tool for rails projects
6
+
7
+ ## Usage
8
+ ```
9
+ $ ./bin/code_hunter --help
10
+ Usage: code_hunter [options]
11
+ --application-path= (default: ./) rails application root path
12
+ ```
13
+
14
+ ## Requirements
15
+ * Ruby >= 1.9
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/code_hunter ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "code_hunter"
5
+
6
+ CodeHunter::Runner.run(ARGV)
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "code_hunter/version"
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "code_hunter"
8
+ gem.version = CodeHunter::VERSION
9
+ gem.authors = ["Ryo Nakamura"]
10
+ gem.email = ["r7kamura@gmail.com"]
11
+ gem.description = "Hunt out weak spots in your rails application"
12
+ gem.summary = "Code hunter"
13
+ gem.homepage = "https://github.com/r7kamura/code_hunter"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "nokogiri"
21
+ gem.add_dependency "activesupport"
22
+ gem.add_dependency "brakeman"
23
+ gem.add_dependency "rails_best_practices"
24
+
25
+ gem.add_development_dependency "rspec", ">= 2.12.0"
26
+ gem.add_development_dependency "simplecov"
27
+ end
@@ -0,0 +1,9 @@
1
+ require "active_support/all"
2
+
3
+ require "code_hunter/version"
4
+ require "code_hunter/option_parser"
5
+ require "code_hunter/runner"
6
+ require "code_hunter/renderer"
7
+ require "code_hunter/brakeman"
8
+ require "code_hunter/rails_best_practices"
9
+ require "code_hunter/git_blamer"
@@ -0,0 +1,40 @@
1
+ require "code_hunter/brakeman/invoker"
2
+ require "code_hunter/brakeman/summarizer"
3
+ require "pathname"
4
+ require "tmpdir"
5
+
6
+ module CodeHunter
7
+ class Brakeman
8
+ TEMPORAL_PATHNAME = Pathname.new("#{Dir.tmpdir}/brakeman.html")
9
+
10
+ attr_reader :options
11
+
12
+ delegate :invoke, :to => :invoker
13
+ delegate :summarize, :to => :summarizer
14
+
15
+ def initialize(options = {})
16
+ @options = options
17
+ end
18
+
19
+ def run
20
+ invoke
21
+ summarize
22
+ ensure
23
+ clean
24
+ end
25
+
26
+ private
27
+
28
+ def clean
29
+ TEMPORAL_PATHNAME.delete if TEMPORAL_PATHNAME.exist?
30
+ end
31
+
32
+ def summarizer
33
+ Summarizer.new(options)
34
+ end
35
+
36
+ def invoker
37
+ Invoker.new(options)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ module CodeHunter
2
+ class Brakeman
3
+ class Invoker
4
+ attr_reader :options
5
+
6
+ def initialize(options = {})
7
+ @options = options
8
+ end
9
+
10
+ def invoke
11
+ system(
12
+ "brakeman",
13
+ "--output", Brakeman::TEMPORAL_PATHNAME.to_s
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ require "nokogiri"
2
+
3
+ module CodeHunter
4
+ class Brakeman
5
+ class Summarizer
6
+ attr_reader :options
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ end
11
+
12
+ def summarize
13
+ warning_elements.map do |element|
14
+ {
15
+ :service => "brakeman",
16
+ :line => find_line(element),
17
+ :path => find_path(element),
18
+ :message => find_message(element),
19
+ }
20
+ end
21
+ end
22
+
23
+ def find_line(element)
24
+ find_message(element)[/near line (\d+)/, 1].try(:to_i)
25
+ end
26
+
27
+ def find_message(element)
28
+ element.child.text.strip
29
+ end
30
+
31
+ def find_path(element)
32
+ element.css("caption").text.strip
33
+ end
34
+
35
+ def warning_elements
36
+ tree.css(".warning_message")
37
+ end
38
+
39
+ def tree
40
+ Nokogiri.HTML(content)
41
+ end
42
+
43
+ def content
44
+ Brakeman::TEMPORAL_PATHNAME.read
45
+ end
46
+
47
+ def summarize_with_file_existence_check
48
+ if Brakeman::TEMPORAL_PATHNAME.exist?
49
+ summarize_without_file_existence_check
50
+ else
51
+ warn "Brakeman output file is not found"
52
+ end
53
+ end
54
+ alias_method_chain :summarize, :file_existence_check
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,26 @@
1
+ module CodeHunter
2
+ class GitBlamer
3
+ def initialize(args)
4
+ @path = args[:path]
5
+ @line = args[:line]
6
+ end
7
+
8
+ def blame
9
+ result = invoke
10
+ parse(result) unless result.empty?
11
+ end
12
+
13
+ def parse(str)
14
+ {
15
+ :sha1 => str.lines.to_a[0][/^[0-9a-f]+/],
16
+ :author => str[/author (.+)/, 1],
17
+ :email => str[/author-mail <(.+)>/, 1],
18
+ :modified_at => str[/author-time (\d+)/, 1].to_i,
19
+ }
20
+ end
21
+
22
+ def invoke
23
+ `git blame #{File.realpath(@path)} -L #@line,+1 -l -e -p`
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,84 @@
1
+ require "optparse"
2
+
3
+ module CodeHunter
4
+ class OptionParser < ::OptionParser
5
+ OPTIONS = [
6
+ "--application-path=", "(default: ./) rails application root path",
7
+ "--format=", "(default: yaml) output format (yaml or json)",
8
+ ]
9
+
10
+ def self.parse!(argv)
11
+ new.parse!(argv)
12
+ end
13
+
14
+ def self.help
15
+ new.help
16
+ end
17
+
18
+ def initialize(*)
19
+ super
20
+ configure_options
21
+ end
22
+
23
+ def parse!(*)
24
+ super
25
+ options
26
+ end
27
+
28
+ def options
29
+ @options ||= {}
30
+ end
31
+
32
+ private
33
+
34
+ def configure_options
35
+ arguments.each do |argument|
36
+ on(argument.key, argument.description) do |value|
37
+ options[argument.to_sym] = value
38
+ end
39
+ end
40
+ end
41
+
42
+ def arguments
43
+ OPTIONS.each_slice(2).map do |key, description|
44
+ Argument.new(key, description)
45
+ end
46
+ end
47
+
48
+ class Argument
49
+ attr_reader :key, :description
50
+
51
+ def initialize(key, description)
52
+ @key = key
53
+ @description = description
54
+ end
55
+
56
+ def to_sym
57
+ str = @key
58
+ str = without_head_hyphen(str)
59
+ str = without_head_no(str)
60
+ str = without_last_equal(str)
61
+ str = underscored(str)
62
+ str.to_sym
63
+ end
64
+
65
+ private
66
+
67
+ def underscored(str)
68
+ str.gsub("-", "_")
69
+ end
70
+
71
+ def without_head_hyphen(str)
72
+ str.gsub(/^--/, "")
73
+ end
74
+
75
+ def without_head_no(str)
76
+ str.gsub(/^no-/, "")
77
+ end
78
+
79
+ def without_last_equal(str)
80
+ str.gsub(/=$/, "")
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,40 @@
1
+ require "code_hunter/rails_best_practices/invoker"
2
+ require "code_hunter/rails_best_practices/summarizer"
3
+ require "pathname"
4
+ require "tmpdir"
5
+
6
+ module CodeHunter
7
+ class RailsBestPractices
8
+ TEMPORAL_PATHNAME = Pathname.new("#{Dir.tmpdir}/rails_best_practices.html")
9
+
10
+ attr_reader :options
11
+
12
+ delegate :invoke, :to => :invoker
13
+ delegate :summarize, :to => :summarizer
14
+
15
+ def initialize(options = {})
16
+ @options = options
17
+ end
18
+
19
+ def run
20
+ invoke
21
+ summarize
22
+ ensure
23
+ clean
24
+ end
25
+
26
+ private
27
+
28
+ def clean
29
+ TEMPORAL_PATHNAME.delete if TEMPORAL_PATHNAME.exist?
30
+ end
31
+
32
+ def summarizer
33
+ Summarizer.new(options)
34
+ end
35
+
36
+ def invoker
37
+ Invoker.new(options)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module CodeHunter
2
+ class RailsBestPractices
3
+ class Invoker
4
+ attr_reader :options
5
+
6
+ def initialize(options = {})
7
+ @options = options
8
+ end
9
+
10
+ def invoke
11
+ system(
12
+ "rails_best_practices",
13
+ "--format", "html",
14
+ "--output-file", RailsBestPractices::TEMPORAL_PATHNAME.to_s
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ require "nokogiri"
2
+
3
+ module CodeHunter
4
+ class RailsBestPractices
5
+ class Summarizer
6
+ attr_reader :options
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ end
11
+
12
+ def summarize
13
+ warning_elements.map do |element|
14
+ {
15
+ :service => "rails_best_practices",
16
+ :line => find_line(element),
17
+ :path => find_path(element),
18
+ :message => find_message(element),
19
+ }
20
+ end
21
+ end
22
+
23
+ def find_line(element)
24
+ element.css(".line").text.strip.to_i
25
+ end
26
+
27
+ def find_path(element)
28
+ element.css(".filename").text.strip
29
+ end
30
+
31
+ def find_message(element)
32
+ element.css(".message").text.strip
33
+ end
34
+
35
+ def warning_elements
36
+ tree.css(".result tbody tr")
37
+ end
38
+
39
+ def tree
40
+ Nokogiri.HTML(content)
41
+ end
42
+
43
+ def content
44
+ RailsBestPractices::TEMPORAL_PATHNAME.read
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ require "json"
2
+ require "yaml"
3
+
4
+ module CodeHunter
5
+ # Render warnings to string with the specified format.
6
+ # As its format, AVAILABLE_FORMATS are available ("yaml" is default).
7
+ class Renderer
8
+ AVAILABLE_FORMATS = %w[json yaml]
9
+
10
+ def self.render(*args)
11
+ new(*args).render
12
+ end
13
+
14
+ def initialize(warnings, options = {})
15
+ @warnings = warnings
16
+ @format = options[:format] || "yaml"
17
+ end
18
+
19
+ def render
20
+ case format
21
+ when "yaml"
22
+ @warnings.to_yaml
23
+ when "json"
24
+ JSON.unparse(@warnings)
25
+ else
26
+ raise ArgumentError, "format #{format} is not available. Use #{AVAILABLE_FORMATS}"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def format
33
+ @format.to_s
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ module CodeHunter
2
+ class Runner
3
+ attr_reader :options
4
+
5
+ def self.run(*args)
6
+ new(*args).run
7
+ end
8
+
9
+ def initialize(argv = [])
10
+ @options = OptionParser.parse!(argv)
11
+ end
12
+
13
+ def run
14
+ warnings = collect_warnings
15
+ warnings = merge_git_metadata(warnings)
16
+ output = Renderer.render(warnings, :format => options[:format])
17
+ puts output
18
+ end
19
+
20
+ def collect_warnings
21
+ services.map(&:run).compact.inject([], :+)
22
+ end
23
+
24
+ def merge_git_metadata(warnings)
25
+ warnings.map do |warning|
26
+ metadata = GitBlamer.new(warning).blame or next
27
+ warning.merge(metadata)
28
+ end.compact
29
+ end
30
+
31
+ private
32
+
33
+ def services
34
+ [Brakeman, RailsBestPractices].map {|klass| klass.new(options) }
35
+ end
36
+
37
+ def run_with_application_path
38
+ Dir.chdir(application_path) { run_without_application_path }
39
+ end
40
+ alias_method_chain :run, :application_path
41
+
42
+ def application_path
43
+ options[:application_path] || "./"
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module CodeHunter
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,59 @@
1
+ require "spec_helper"
2
+ require "active_support/core_ext/string/strip"
3
+
4
+ module CodeHunter
5
+ describe Renderer do
6
+ describe ".render(warnings, options = {})" do
7
+ subject do
8
+ described_class.render(warnings, options)
9
+ end
10
+
11
+ let(:warnings) do
12
+ [{ :a => :b }]
13
+ end
14
+
15
+ let(:options) do
16
+ { :format => format }
17
+ end
18
+
19
+ shared_examples_for "render warnings with yaml" do
20
+ it "renders warnings as yaml" do
21
+ should == <<-EOS.strip_heredoc
22
+ ---
23
+ - :a: :b
24
+ EOS
25
+ end
26
+ end
27
+
28
+ context "when options[:format] is not specified" do
29
+ let(:format) do
30
+ nil
31
+ end
32
+ include_examples "render warnings with yaml"
33
+ end
34
+
35
+ context "when options[:format] is :yaml" do
36
+ let(:format) do
37
+ :yaml
38
+ end
39
+ include_examples "render warnings with yaml"
40
+ end
41
+
42
+ context "when options[:format] is :json" do
43
+ let(:format) do
44
+ :json
45
+ end
46
+ it "renders warnings as json" do
47
+ should == '[{"a":"b"}]'
48
+ end
49
+ end
50
+
51
+ context "when options[:format] is invalid" do
52
+ let(:format) do
53
+ :invalid
54
+ end
55
+ it { expect { subject }.to raise_error(ArgumentError) }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require "code_hunter"
3
+
4
+ RSpec.configure do |config|
5
+ config.treat_symbols_as_metadata_keys_with_true_values = true
6
+ config.run_all_when_everything_filtered = true
7
+ config.filter_run :focus
8
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: code_hunter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryo Nakamura
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: brakeman
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rails_best_practices
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: 2.12.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 2.12.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: simplecov
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Hunt out weak spots in your rails application
111
+ email:
112
+ - r7kamura@gmail.com
113
+ executables:
114
+ - code_hunter
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - bin/code_hunter
124
+ - code_hunter.gemspec
125
+ - lib/code_hunter.rb
126
+ - lib/code_hunter/brakeman.rb
127
+ - lib/code_hunter/brakeman/invoker.rb
128
+ - lib/code_hunter/brakeman/summarizer.rb
129
+ - lib/code_hunter/git_blamer.rb
130
+ - lib/code_hunter/option_parser.rb
131
+ - lib/code_hunter/rails_best_practices.rb
132
+ - lib/code_hunter/rails_best_practices/invoker.rb
133
+ - lib/code_hunter/rails_best_practices/summarizer.rb
134
+ - lib/code_hunter/renderer.rb
135
+ - lib/code_hunter/runner.rb
136
+ - lib/code_hunter/version.rb
137
+ - spec/code_hunter/renderer_spec.rb
138
+ - spec/spec_helper.rb
139
+ homepage: https://github.com/r7kamura/code_hunter
140
+ licenses: []
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ! '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 1.8.24
160
+ signing_key:
161
+ specification_version: 3
162
+ summary: Code hunter
163
+ test_files:
164
+ - spec/code_hunter/renderer_spec.rb
165
+ - spec/spec_helper.rb