code_poetry 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: cb3462469131688d286ff7f768db70ae4de1e12a
4
+ data.tar.gz: 2c18647925f0741cb981670ad94a53890cacf7fd
5
+ SHA512:
6
+ metadata.gz: 4c348c339d702a853aaa6cdf72c41bfb547f6d992223de44a20b2e28e6c5391ab4c861f4e72358eda2aa829b83e8dfd244d392137279c8e215ddd0bb75a5d1e9
7
+ data.tar.gz: d0e0ea9ffa4aa906eb797931e85e46502ad409e1152c0e4f48994fd15d44ff6fd9e02427a110a8eb5b5b626ab24af52ac6361550cc08678d057fe0dd9e45e369
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_poetry.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Bastian Bartmann
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,41 @@
1
+ # Code Poetry
2
+
3
+ The poor men's [Code Climate][cc].
4
+
5
+ Analyzes the code of your Rails app and generates a straightforward HTML report.
6
+
7
+ Currently it uses the following metrics:
8
+
9
+ * Lines of Code
10
+ * Churns [[Churn][ch]]
11
+ * Code Complexity [[Flog][fl]]
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'code_poetry'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ ## Usage
24
+
25
+ Excecute from your application root:
26
+
27
+ $ rake metrics
28
+
29
+ This will generate a HTML report to ```metrics/index.html```.
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
38
+
39
+ [cc]: https://codeclimate.com
40
+ [ch]: https://github.com/danmayer/churn
41
+ [fl]: https://github.com/seattlerb/flog
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'code_poetry/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "code_poetry"
8
+ spec.version = CodePoetry::VERSION
9
+ spec.authors = ["Bastian Bartmann"]
10
+ spec.email = ["babartmann@gmail.com"]
11
+ spec.description = %q{Analyzes the code of your Rails app and generates a straightforward HTML report.}
12
+ spec.summary = %q{The poor men's Code Climate}
13
+ spec.homepage = "https://github.com/coding-chimp/code_poetry"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
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_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_runtime_dependency 'code_poetry-html', ['~> 0.0']
25
+ spec.add_runtime_dependency 'code_metrics', ['~> 0.1']
26
+ spec.add_runtime_dependency 'churn', ['~> 0.0']
27
+ spec.add_runtime_dependency 'flog', ['~> 4.2']
28
+ end
@@ -0,0 +1,69 @@
1
+ require 'churn/churn_calculator'
2
+ require 'flog_cli'
3
+ require 'ripper'
4
+
5
+ module CodePoetry
6
+ class Calculator
7
+ def initialize(files)
8
+ @files = files
9
+ @churns = {}
10
+ @stats = []
11
+ end
12
+
13
+ def calculate
14
+ puts 'Calculating'
15
+
16
+ measure_churns
17
+
18
+ @files.each do |file|
19
+ stat = Stat.new(file)
20
+
21
+ stat.set_churns(@churns[file])
22
+ measure_flog(stat)
23
+
24
+ @stats << stat
25
+ end
26
+
27
+ @stats
28
+ end
29
+
30
+ private
31
+
32
+ def measure_churns
33
+ churns = Churn::ChurnCalculator.new(history: false).report(false)
34
+
35
+ churns[:churn][:changes].each do |churn|
36
+ @churns[churn[:file_path]] = churn[:times_changed]
37
+ end
38
+ end
39
+
40
+ def measure_flog(stat)
41
+ flogger = FlogCLI.new(all: true)
42
+ flogger.flog(stat.file)
43
+ flogger.calculate
44
+
45
+ outside_methods = 0
46
+
47
+ unless flogger.scores.empty?
48
+ klass = flogger.scores.first[0]
49
+ stat.complexity = flogger.total_score.round(2)
50
+ stat.complexity_per_method = flogger.average.round(2)
51
+
52
+ flogger.method_scores[klass].each do |name, score|
53
+ next if score.nil?
54
+
55
+ name = (name.match(/#(.+)/) || name.match(/::(.+)/))[1]
56
+ method = stat.get_method(name)
57
+
58
+ if method
59
+ method[:complexity] = score.round(2)
60
+ else
61
+ outside_methods += score
62
+ end
63
+ end
64
+ end
65
+
66
+ stat.set_outside_of_methods_complexity(outside_methods.round(2))
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ require 'code_poetry/calculator'
2
+ require 'code_poetry/stat'
3
+ require 'code_poetry/formatter'
4
+ require 'code_poetry-html'
5
+
6
+ module CodePoetry
7
+ class CLI
8
+ def self.excecute
9
+ files = Array(FileList['app/**/*.rb']).compact
10
+ calculator = Calculator.new(files)
11
+ stats = calculator.calculate
12
+
13
+ formatter = CodePoetry::Formatter::HTMLFormatter.new
14
+ formatter.format(stats)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,4 @@
1
+ module CodePoetry
2
+ module Formatter
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails'
2
+
3
+ module CodePoetry
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :code_poetry
6
+
7
+ rake_tasks do
8
+ load "tasks/code_poetry.rake"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,133 @@
1
+ require 'code_poetry/warning_scanner'
2
+
3
+ module CodePoetry
4
+ class Stat
5
+ attr_accessor :file, :name, :lines, :lines_of_code, :churns, :complexity, :details, :complexity_per_method
6
+
7
+ def initialize(file)
8
+ @lines_of_code, @churns, @complexity, @complexity_per_method = 0, 0, 0, 0
9
+ @file = file
10
+ @lines = {}
11
+ @details = []
12
+
13
+ parse_file
14
+ end
15
+
16
+ def set_churns(churns)
17
+ @churns = churns if churns
18
+ end
19
+
20
+ def get_method(name)
21
+ method = details.select{|method| method[:name] == name}
22
+ method[0] unless method.empty?
23
+ end
24
+
25
+ def set_method_complexity(name, complexity)
26
+ method = details.select{|method| method[:name] == name}
27
+ method[0][:complexity] = complexity unless method.empty?
28
+ end
29
+
30
+ def set_outside_of_methods_complexity(complexity)
31
+ @details.unshift({name: "none", complexity: complexity})
32
+ end
33
+
34
+ def get_method_at_line(line)
35
+ @method = nil
36
+
37
+ @details.each do |detail|
38
+ next if detail[:first_line].nil?
39
+
40
+ if detail[:last_line].nil?
41
+ if detail[:first_line] == line
42
+ @method = detail
43
+ break
44
+ else
45
+ next
46
+ end
47
+ end
48
+
49
+ if detail[:first_line] <= line && detail[:last_line] >= line
50
+ @method = detail
51
+ break
52
+ end
53
+ end
54
+
55
+ @method
56
+ end
57
+
58
+ private
59
+
60
+ def parse_file
61
+ @content = File.open(@file, "r").read
62
+ @indentation_warnings = indentation_warnings
63
+
64
+ set_name
65
+ set_lines
66
+ set_methods
67
+ end
68
+
69
+ def set_name
70
+ @content = File.open(@file, "r").read
71
+
72
+ if match = /^\s*class\s+(\S+)/.match(@content) || /^\s*module\s+(\S+)/.match(@content)
73
+ @name = match[1]
74
+ end
75
+ end
76
+
77
+ def set_lines
78
+ @content.each_line.with_index(1) do |line, i|
79
+ @lines[i] = line
80
+ next if line =~ /^\s*$/
81
+ next if line =~ /^\s*#/
82
+ @lines_of_code += 1
83
+ end
84
+ end
85
+
86
+ def set_methods
87
+ sexp = Ripper.sexp(@content)
88
+ scan_sexp(sexp)
89
+ end
90
+
91
+ def scan_sexp(sexp)
92
+ sexp.each do |element|
93
+ next unless element.kind_of?(Array)
94
+
95
+ case element.first
96
+ when :def, :defs
97
+ name, first_line = find_method_params(element)
98
+
99
+ if @indentation_warnings['def'] && @indentation_warnings['def'].any? { |first, last| first == first_line }
100
+ warning = @indentation_warnings['def'].select{|first, last| first == first_line}[0]
101
+ last_line = warning[1]
102
+ else
103
+ last_line = find_last_line(name, first_line)
104
+ end
105
+
106
+ @details << {name: name, first_line: first_line, last_line: last_line, complexity: 0}
107
+ else
108
+ scan_sexp(element)
109
+ end
110
+ end
111
+ end
112
+
113
+ def find_method_params(sexp)
114
+ if sexp.first == :def
115
+ sexp[1].flatten[1,2]
116
+ else
117
+ sexp[3].flatten[1,2]
118
+ end
119
+ end
120
+
121
+ def find_last_line(token_name, line)
122
+ token_indentation = @lines[line].index('def')
123
+
124
+ last_line = @lines.values[line..-1].index { |l| l =~ %r(\A\s{#{token_indentation}}end\s*\z) }
125
+ last_line ? last_line + line + 1 : nil
126
+ end
127
+
128
+ def indentation_warnings
129
+ warning_scanner = WarningScanner.new
130
+ warning_scanner.scan(@content)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module CodePoetry
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'open3'
2
+
3
+ module CodePoetry
4
+ class WarningScanner
5
+ INDENTATION_WARNING_REGEXP = /at 'end' with '(def|class|module)' at (\d+)\z/
6
+
7
+ def scan(source)
8
+ if defined? Bundler
9
+ Bundler.with_clean_env do
10
+ status, @warnings, process = validate(source)
11
+ end
12
+ else
13
+ status, @warnings, process = validate(source)
14
+ end
15
+
16
+ @indentation_warnings = parse_warnings
17
+ end
18
+
19
+ private
20
+
21
+ def validate(source)
22
+ Open3.capture3('ruby -wc', stdin_data: source)
23
+ end
24
+
25
+ def parse_warnings
26
+ @warnings.split("\n").inject({}) do |warnings, warning|
27
+ token, line, end_line = extract_indentation_mismatch(warning)
28
+ if token == 'def'
29
+ warnings[token] ||= []
30
+ warnings[token] << [line.to_i, end_line.to_i]
31
+ end
32
+ warnings
33
+ end
34
+ end
35
+
36
+ def extract_indentation_mismatch(warning_line)
37
+ _, end_line_num, warning_type, warning_body = warning_line.split(':').map(&:strip)
38
+ return nil unless warning_type == 'warning'
39
+ return nil unless warning_body =~ /at 'end' with '(def|class|module)' at (\d+)\z/
40
+
41
+ warning_body.match(INDENTATION_WARNING_REGEXP)[1..2] << end_line_num
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ require 'code_poetry/version'
2
+ require 'fileutils'
3
+
4
+ module CodePoetry
5
+ if defined?(Rails)
6
+ require 'code_poetry/railtie'
7
+ else
8
+ load 'tasks/code_poetry.rake'
9
+ end
10
+
11
+ class << self
12
+
13
+ def root
14
+ return @root if defined? @root
15
+ @root = File.expand_path(Dir.getwd)
16
+ end
17
+
18
+ def coverage_path
19
+ coverage_path = File.expand_path('metrics', root)
20
+ FileUtils.mkdir_p coverage_path
21
+ coverage_path
22
+ end
23
+
24
+ def project_name
25
+ return @project_name if defined? @project_name
26
+ @project_name = File.basename(root.split('/').last).capitalize.gsub('_', ' ')
27
+ end
28
+
29
+ def project_name
30
+ File.basename(root.split('/').last).capitalize.gsub('_', ' ')
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ desc "Generate code metrics"
2
+ task :metrics do
3
+ require 'code_poetry/cli'
4
+ CodePoetry::CLI.excecute
5
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: code_poetry
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Bastian Bartmann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: code_poetry-html
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: code_metrics
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: churn
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: flog
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '4.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '4.2'
97
+ description: Analyzes the code of your Rails app and generates a straightforward HTML
98
+ report.
99
+ email:
100
+ - babartmann@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - .gitignore
106
+ - Gemfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - code_poetry.gemspec
111
+ - lib/code_poetry.rb
112
+ - lib/code_poetry/calculator.rb
113
+ - lib/code_poetry/cli.rb
114
+ - lib/code_poetry/formatter.rb
115
+ - lib/code_poetry/railtie.rb
116
+ - lib/code_poetry/stat.rb
117
+ - lib/code_poetry/version.rb
118
+ - lib/code_poetry/warning_scanner.rb
119
+ - lib/tasks/code_poetry.rake
120
+ homepage: https://github.com/coding-chimp/code_poetry
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.0.3
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: The poor men's Code Climate
144
+ test_files: []
145
+ has_rdoc: