maximus 0.1.3 → 0.1.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.
- 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
|