maximus 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/README.md +35 -14
- data/lib/maximus/cli.rb +1 -0
- data/lib/maximus/config.rb +148 -135
- data/lib/maximus/git_control.rb +96 -96
- data/lib/maximus/helper.rb +1 -0
- data/lib/maximus/lint.rb +119 -118
- data/lib/maximus/lints/brakeman.rb +14 -14
- data/lib/maximus/lints/railsbp.rb +13 -13
- data/lib/maximus/reporter/git-lines.sh +40 -11
- data/lib/maximus/statistic.rb +30 -25
- data/lib/maximus/statistics/phantomas.rb +11 -11
- data/lib/maximus/statistics/stylestats.rb +52 -55
- data/lib/maximus/statistics/wraith.rb +21 -25
- data/lib/maximus/version.rb +1 -1
- data/maximus.gemspec +4 -0
- data/roadmap.md +3 -6
- data/spec/maximus/config_spec.rb +44 -0
- data/spec/spec_helper.rb +4 -0
- metadata +51 -3
data/lib/maximus/git_control.rb
CHANGED
@@ -10,15 +10,10 @@ module Maximus
|
|
10
10
|
#
|
11
11
|
# Inherits settings from {Config#initialize}
|
12
12
|
# @param opts [Hash] options passed directly to config
|
13
|
-
# @option opts [Boolean] :is_dev (false) whether or not the class was initialized from the command line
|
14
|
-
# This is set here again in case only GitControl needs to be directly called (outside of command line)
|
15
13
|
# @option opts [Config object] :config custom Maximus::Config object
|
16
14
|
# @option opts [String] :commit accepts sha, "working", "last", or "master".
|
17
|
-
# @return [void] this method is used to set up instance variables
|
18
15
|
def initialize(opts = {})
|
19
|
-
opts[:
|
20
|
-
|
21
|
-
opts[:config] ||= Maximus::Config.new({commit: opts[:commit], is_dev: opts[:is_dev] })
|
16
|
+
opts[:config] ||= Maximus::Config.new({ commit: opts[:commit] })
|
22
17
|
@config ||= opts[:config]
|
23
18
|
@settings ||= @config.settings
|
24
19
|
@psuedo_commit = (!@settings[:commit].blank? && (@settings[:commit] == 'working' || @settings[:commit] == 'last' || @settings[:commit] == 'master') )
|
@@ -26,19 +21,19 @@ module Maximus
|
|
26
21
|
end
|
27
22
|
|
28
23
|
# 30,000 foot view of a commit
|
29
|
-
#
|
30
|
-
# @param commitsha [String] the sha of the commit
|
24
|
+
# @param commit_sha [String] the sha of the commit
|
31
25
|
# @return [Hash] commit data
|
32
|
-
def commit_export(
|
33
|
-
ce_commit = vccommit(
|
26
|
+
def commit_export(commit_sha = sha)
|
27
|
+
ce_commit = vccommit(commit_sha)
|
34
28
|
ce_diff = diff(ce_commit, @g.object('HEAD^'))
|
35
29
|
{
|
36
|
-
|
30
|
+
commit_sha: commit_sha,
|
37
31
|
branch: branch,
|
38
32
|
message: ce_commit.message,
|
39
33
|
remote_repo: remote,
|
40
34
|
git_author: ce_commit.author.name,
|
41
35
|
git_author_email: ce_commit.author.email,
|
36
|
+
commit_date: ce_commit.author.date.to_s,
|
42
37
|
diff: ce_diff
|
43
38
|
}
|
44
39
|
end
|
@@ -75,7 +70,7 @@ module Maximus
|
|
75
70
|
# The space here is important because git-lines checks for a second arg,
|
76
71
|
# and if one is present, it runs git diff without a commit
|
77
72
|
# or a comparison to a commit.
|
78
|
-
git_diff = @psuedo_commit ? [
|
73
|
+
git_diff = @psuedo_commit ? ["git #{sha1}"] : `git rev-list #{sha1}..#{sha2} --no-merges`.split("\n")
|
79
74
|
|
80
75
|
# Include the first sha because rev-list is doing a traversal
|
81
76
|
# So sha1 is never included
|
@@ -191,7 +186,11 @@ module Maximus
|
|
191
186
|
end
|
192
187
|
# @todo better way to silence git, in case there's a real error?
|
193
188
|
quietly {
|
194
|
-
|
189
|
+
if base_branch == "maximus_#{sha}"
|
190
|
+
@g.branch('master').checkout
|
191
|
+
else
|
192
|
+
@g.branch(base_branch).checkout
|
193
|
+
end
|
195
194
|
@g.branch("maximus_#{sha}").delete
|
196
195
|
} unless @psuedo_commit
|
197
196
|
end
|
@@ -201,98 +200,99 @@ module Maximus
|
|
201
200
|
|
202
201
|
protected
|
203
202
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
203
|
+
# Get list of file paths
|
204
|
+
#
|
205
|
+
# @param files [Hash] hash of files denoted by key 'filename'
|
206
|
+
# @param ext [String] file extension - different extensions are joined different ways
|
207
|
+
# @return [String] file paths delimited by comma or space
|
208
|
+
def lint_file_paths(files, ext)
|
209
|
+
file_list = files.map { |f| f[:filename] }.compact
|
210
|
+
# Lints accept files differently
|
211
|
+
ext == :ruby ? file_list.join(' ') : file_list.join(',')
|
212
|
+
end
|
214
213
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
214
|
+
# Determine which lines were added (where and how many) in a commit
|
215
|
+
#
|
216
|
+
# @example output from method
|
217
|
+
# {
|
218
|
+
# 'filename': [
|
219
|
+
# '0..10',
|
220
|
+
# '11..14'
|
221
|
+
# ]
|
222
|
+
# }
|
223
|
+
#
|
224
|
+
# @param git_sha [String] sha of the commit
|
225
|
+
# @return [Hash] ranges by lines added in a commit by file name
|
226
|
+
def lines_added(git_sha)
|
227
|
+
new_lines = {}
|
228
|
+
lines_added = `#{File.join(File.dirname(__FILE__), 'reporter/git-lines.sh')} #{git_sha}`.split("\n")
|
229
|
+
lines_added.each do |filename|
|
230
|
+
fsplit = filename.split(':')
|
231
|
+
# if file isn't already part of the array
|
232
|
+
new_lines[fsplit[0]] ||= []
|
233
|
+
new_lines[fsplit[0]] << fsplit[1] unless fsplit[1].nil?
|
234
|
+
# no repeats
|
235
|
+
new_lines[fsplit[0]].uniq!
|
236
|
+
end
|
237
|
+
new_lines.delete("/dev/null")
|
238
|
+
new_lines
|
237
239
|
end
|
238
|
-
new_lines.delete("/dev/null")
|
239
|
-
new_lines
|
240
|
-
end
|
241
240
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
end
|
241
|
+
# Get last commit on current branch
|
242
|
+
# @return [String] sha
|
243
|
+
def sha
|
244
|
+
@g.object('HEAD').sha
|
245
|
+
end
|
248
246
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
end
|
247
|
+
# Get current branch name
|
248
|
+
# @return [String]
|
249
|
+
def branch
|
250
|
+
`env -i git rev-parse --abbrev-ref HEAD`.strip!
|
251
|
+
end
|
255
252
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
end
|
253
|
+
# Get last commit on the master branch
|
254
|
+
# @return [Git::Object]
|
255
|
+
def master_commit
|
256
|
+
@g.branches[:master].gcommit
|
257
|
+
end
|
262
258
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
end
|
259
|
+
# Store last commit as Ruby Git::Object
|
260
|
+
# @param commit_sha [String]
|
261
|
+
# @return [Git::Object]
|
262
|
+
def vccommit(commit_sha = sha)
|
263
|
+
@g.gcommit(commit_sha)
|
264
|
+
end
|
270
265
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
@
|
276
|
-
|
266
|
+
# Get general stats of commit on HEAD versus last commit on master branch
|
267
|
+
# @modified 0.1.4
|
268
|
+
# @param new_commit [Git::Object]
|
269
|
+
# @param old_commit [Git::Object]
|
270
|
+
# @return [Git::Diff] hash of abbreviated, useful stats with added lines
|
271
|
+
def diff(new_commit = vccommit, old_commit = master_commit)
|
272
|
+
stats = @g.diff(new_commit, old_commit).stats
|
273
|
+
return if lines.blank? || diff.blank?
|
274
|
+
lines.each do |filename, filelines|
|
275
|
+
stats[:files][filename][:lines_added] = filelines if stats[:files].has_key?(filename)
|
276
|
+
end
|
277
|
+
stats
|
278
|
+
end
|
277
279
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
end
|
280
|
+
# Get remote URL
|
281
|
+
# @return [String, nil] nil returns if remotes is blank
|
282
|
+
def remote
|
283
|
+
@g.remotes.first.url unless @g.remotes.blank?
|
284
|
+
end
|
284
285
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
end
|
286
|
+
# Define associations to linters based on file extension
|
287
|
+
# @return [Hash] linters and extension arrays
|
288
|
+
def associations
|
289
|
+
{
|
290
|
+
scss: ['scss', 'sass'],
|
291
|
+
js: ['js'],
|
292
|
+
ruby: ['rb', 'Gemfile', 'lock', 'yml', 'Rakefile', 'ru', 'rdoc'],
|
293
|
+
rails: ['slim', 'haml']
|
294
|
+
}
|
295
|
+
end
|
296
296
|
|
297
297
|
end
|
298
298
|
end
|
data/lib/maximus/helper.rb
CHANGED
data/lib/maximus/lint.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
module Maximus
|
4
|
+
|
5
|
+
# Parent class for all lints (inherited by children)
|
4
6
|
# @since 0.1.0
|
5
7
|
# @attr_accessor output [Hash] result of a lint parsed by Lint#refine
|
6
8
|
class Lint
|
@@ -21,11 +23,12 @@ module Maximus
|
|
21
23
|
# end
|
22
24
|
#
|
23
25
|
# Inherits settings from {Config#initialize}
|
26
|
+
# @see Config#initialize
|
24
27
|
#
|
25
28
|
# @param opts [Hash] ({}) options passed directly to the lint
|
26
|
-
# @option
|
29
|
+
# @option opts [Hash] :git_files filename: file location
|
27
30
|
# @see GitControl#lints_and_stats
|
28
|
-
# @option
|
31
|
+
# @option opts [Array, String] :file_paths lint only specific files or directories
|
29
32
|
# Accepts globs too
|
30
33
|
# which is used to define paths from the URL
|
31
34
|
# @option opts [Config object] :config custom Maximus::Config object
|
@@ -33,8 +36,8 @@ module Maximus
|
|
33
36
|
def initialize(opts = {})
|
34
37
|
|
35
38
|
# Only run the config once
|
36
|
-
|
37
|
-
@settings
|
39
|
+
@config = opts[:config] || Maximus::Config.new(opts)
|
40
|
+
@settings = @config.settings
|
38
41
|
|
39
42
|
@git_files = opts[:git_files]
|
40
43
|
@path = opts[:file_paths] || @settings[:file_paths]
|
@@ -89,151 +92,149 @@ module Maximus
|
|
89
92
|
@output[:lint_conventions] = lint_conventions
|
90
93
|
@output[:lint_refactors] = lint_refactors
|
91
94
|
lint_count = (lint_errors.length + lint_warnings.length + lint_conventions.length + lint_refactors.length)
|
92
|
-
if
|
95
|
+
if @config.is_dev?
|
93
96
|
puts lint_dev_format(data) unless data.blank?
|
94
97
|
puts lint_summarize
|
95
98
|
lint_ceiling lint_count
|
96
99
|
else
|
97
|
-
|
100
|
+
@config.log.info lint_summarize
|
98
101
|
# Because this should be returned in the format it was received
|
99
102
|
@output[:raw_data] = data.to_json
|
100
103
|
end
|
101
|
-
|
104
|
+
@config.destroy_temp(@task)
|
102
105
|
@output
|
103
106
|
end
|
104
107
|
|
105
108
|
|
106
109
|
protected
|
107
110
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
111
|
+
# List all files inspected
|
112
|
+
#
|
113
|
+
# @param ext [String] extension to search for
|
114
|
+
# @param delimiter [String] comma or space separated
|
115
|
+
# @param remove [String] remove from all file names
|
116
|
+
# @return all_files [Array<string>] list of file names
|
117
|
+
def files_inspected(ext, delimiter = ',', remove = @settings[:root_dir])
|
118
|
+
@path.is_a?(Array) ? @path.split(delimiter) : file_list(@path, ext, remove)
|
119
|
+
end
|
117
120
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
121
|
+
# Compare lint output with lines changed in commit
|
122
|
+
#
|
123
|
+
# @param lint [Hash] output lint data
|
124
|
+
# @param files [Hash<String: String>] filename: filepath
|
125
|
+
# @return [Array] lints that match the lines in commit
|
126
|
+
def relevant_lints(lint, files)
|
127
|
+
all_files = {}
|
128
|
+
files.each do |file|
|
129
|
+
|
130
|
+
# sometimes data will be blank but this is good - it means no errors raised in the lint
|
131
|
+
unless lint.blank?
|
132
|
+
lint_file = lint[file[:filename].to_s]
|
133
|
+
|
134
|
+
expanded = lines_added_to_range(file)
|
135
|
+
revert_name = file[:filename].gsub("#{@settings[:root_dir]}/", '')
|
136
|
+
unless lint_file.blank?
|
137
|
+
all_files[revert_name] = []
|
138
|
+
|
139
|
+
# @todo originally I tried .map and delete_if, but this works,
|
140
|
+
# and the other method didn't cover all bases.
|
141
|
+
# Gotta be a better way to write this though
|
142
|
+
lint_file.each do |l|
|
143
|
+
if expanded.include?(l['line'].to_i)
|
144
|
+
all_files[revert_name] << l
|
145
|
+
end
|
142
146
|
end
|
147
|
+
# If there's nothing there, then it definitely isn't a relevant lint
|
148
|
+
all_files.delete(revert_name) if all_files[revert_name].blank?
|
143
149
|
end
|
144
|
-
|
145
|
-
|
150
|
+
else
|
151
|
+
# Optionally store the filename with a blank array
|
152
|
+
# @example all_files[file[:filename].to_s.gsub("#{@settings[:root_dir]}/", '')] = []
|
146
153
|
end
|
147
|
-
else
|
148
|
-
# Optionally store the filename with a blank array
|
149
|
-
# @example all_files[file[:filename].to_s.gsub("#{@settings[:root_dir]}/", '')] = []
|
150
154
|
end
|
155
|
+
@output[:files_linted] = all_files.keys
|
156
|
+
all_files
|
151
157
|
end
|
152
|
-
@output[:files_linted] = all_files.keys
|
153
|
-
all_files
|
154
|
-
end
|
155
158
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
159
|
+
# Look for a config defined from Config#initialize
|
160
|
+
#
|
161
|
+
# @since 0.1.2
|
162
|
+
# @param search_for [String]
|
163
|
+
# @return [String, Boolean] path to temp file
|
164
|
+
def temp_config(search_for)
|
165
|
+
return false if @settings.nil?
|
166
|
+
@settings[search_for.to_sym].blank? ? false : @settings[search_for.to_sym]
|
167
|
+
end
|
165
168
|
|
166
169
|
|
167
170
|
private
|
168
171
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
172
|
+
# Send abbreviated results to console or to the log
|
173
|
+
#
|
174
|
+
# @return [String] console message to display
|
175
|
+
def lint_summarize
|
176
|
+
puts "\n" if @config.is_dev?
|
177
|
+
|
178
|
+
puts "#{'Warning'.color(:red)}: #{@output[:lint_errors].length} errors found in #{@task.to_s}" if @output[:lint_errors].length > 0
|
179
|
+
|
180
|
+
success = @task.to_s.color(:green)
|
181
|
+
success += ": "
|
182
|
+
success += "[#{@output[:lint_warnings].length}]".color(:yellow)
|
183
|
+
success += " " + "[#{@output[:lint_errors].length}]".color(:red)
|
184
|
+
if @task == 'rubocop'
|
185
|
+
success += " " + "[#{@output[:lint_conventions].length}]".color(:cyan)
|
186
|
+
success += " " + "[#{@output[:lint_refactors].length}]".color(:white)
|
187
|
+
end
|
185
188
|
|
186
|
-
|
187
|
-
|
189
|
+
success
|
190
|
+
end
|
188
191
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
abort errormsg unless truthy(go_on)
|
192
|
+
# If there's just too much to handle, through a warning.
|
193
|
+
#
|
194
|
+
# @param lint_length [Integer] count of how many lints
|
195
|
+
# @return [String] console message to display
|
196
|
+
def lint_ceiling(lint_length)
|
197
|
+
if lint_length > 100
|
198
|
+
lint_dev_format
|
199
|
+
failed_task = "#{@task}".color(:green)
|
200
|
+
errors = Rainbow("#{lint_length} failures.").red
|
201
|
+
errormsg = ["You wouldn't stand a chance in Rome.\nResolve thy errors and train with #{failed_task} again.", "The gods frown upon you, mortal.\n#{failed_task}. Again.", "Do not embarrass the city. Fight another day. Use #{failed_task}.", "You are without honor. Replenish it with another #{failed_task}.", "You will never claim the throne with a performance like that.", "Pompeii has been lost.", "A wise choice. Do not be discouraged from another #{failed_task}."].sample
|
202
|
+
errormsg += "\n\n"
|
203
|
+
|
204
|
+
go_on = prompt "\n#{errors} Continue? (y/n) "
|
205
|
+
abort errormsg unless truthy(go_on)
|
206
|
+
end
|
205
207
|
end
|
206
|
-
end
|
207
208
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
pretty_output += "\n"
|
220
|
-
error_list.each do |message|
|
221
|
-
pretty_output += case message['severity']
|
222
|
-
when 'warning' then 'W'.color(:yellow)
|
223
|
-
when 'error' then 'E'.color(:red)
|
224
|
-
when 'convention' then 'C'.color(:cyan)
|
225
|
-
when 'refactor' then 'R'.color(:white)
|
226
|
-
else '?'.color(:blue)
|
227
|
-
end
|
228
|
-
pretty_output += ' '
|
229
|
-
pretty_output += message['line'].to_s.color(:blue)
|
230
|
-
pretty_output += " #{message['linter'].color(:green)}: "
|
231
|
-
pretty_output += message['reason']
|
209
|
+
# Dev display, executed only when called from command line
|
210
|
+
#
|
211
|
+
# @param errors [Hash] data from lint
|
212
|
+
# @return [String] console message to display
|
213
|
+
def lint_dev_format(errors = @output[:raw_data])
|
214
|
+
return if errors.blank?
|
215
|
+
pretty_output = ''
|
216
|
+
errors.each do |filename, error_list|
|
217
|
+
pretty_output += "\n"
|
218
|
+
filename = filename.gsub("#{@settings[:root_dir]}/", '')
|
219
|
+
pretty_output += filename.color(:cyan).underline
|
232
220
|
pretty_output += "\n"
|
221
|
+
error_list.each do |message|
|
222
|
+
pretty_output += case message['severity']
|
223
|
+
when 'warning' then 'W'.color(:yellow)
|
224
|
+
when 'error' then 'E'.color(:red)
|
225
|
+
when 'convention' then 'C'.color(:cyan)
|
226
|
+
when 'refactor' then 'R'.color(:white)
|
227
|
+
else '?'.color(:blue)
|
228
|
+
end
|
229
|
+
pretty_output += ' '
|
230
|
+
pretty_output += message['line'].to_s.color(:blue)
|
231
|
+
pretty_output += " #{message['linter'].color(:green)}: "
|
232
|
+
pretty_output += message['reason']
|
233
|
+
pretty_output += "\n"
|
234
|
+
end
|
233
235
|
end
|
236
|
+
pretty_output
|
234
237
|
end
|
235
|
-
pretty_output
|
236
|
-
end
|
237
238
|
|
238
239
|
end
|
239
240
|
end
|