zubat 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/zubat +2 -2
- data/lib/zubat/analizer.rb +18 -7
- data/lib/zubat/cli/app.rb +101 -0
- data/lib/zubat/commit.rb +4 -0
- data/lib/zubat/generator.rb +8 -4
- data/lib/zubat/git_command_wrapper.rb +23 -0
- data/lib/zubat/version.rb +1 -1
- data/templates/chart.html.erb +50 -19
- metadata +3 -3
- data/lib/zubat/cli.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: baa42361d5aa2f8268e3d5be1e21cc2e0ae7e6b1ce839b43fe9b410f8678e92c
|
4
|
+
data.tar.gz: da15e758d143aa4ae0521d2281577e33cbd08dccd209de20e116068846bc715c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 824f35f0dbaa6fa6ddb9cb37bf47f2f8d3221c393beae470465a979fa6773a807c07a305dfd737e81c53935c7f6cb6f7daba525c1204243803d8d36096656625
|
7
|
+
data.tar.gz: abd8a26623ed6a4d60bec503551463de0c9cbb7e551b57e6c69702d37280f616fd2158cecfe78906252de3db40ccc6b0f2d8aeeb28154a25e67ea78e061c7033
|
data/exe/zubat
CHANGED
data/lib/zubat/analizer.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Zubat
|
4
4
|
class Analizer
|
5
|
-
AnalizedResult = Data.define(:label, :stat)
|
5
|
+
AnalizedResult = Data.define(:label, :commit, :stat)
|
6
6
|
|
7
7
|
# @param stat [Hash<String, Hash<String, Integer>>]
|
8
8
|
Stat = Data.define(:stat) do
|
@@ -24,6 +24,10 @@ module Zubat
|
|
24
24
|
stat.fetch(file).fetch(:smells).fetch(label, 0)
|
25
25
|
end
|
26
26
|
|
27
|
+
def diff(file)
|
28
|
+
stat.fetch(file, {}).fetch(:diff, nil)
|
29
|
+
end
|
30
|
+
|
27
31
|
def each(*, **, &block)
|
28
32
|
stat.each(*, **, &block)
|
29
33
|
end
|
@@ -33,24 +37,31 @@ module Zubat
|
|
33
37
|
stat = {}
|
34
38
|
|
35
39
|
files.each do |file|
|
36
|
-
|
40
|
+
diff = commit.diff(file:)
|
41
|
+
|
42
|
+
next if diff.empty?
|
43
|
+
|
44
|
+
stat[file] = {}
|
45
|
+
|
46
|
+
stat[file].merge!(diff:)
|
37
47
|
|
38
48
|
code = commit.show(file:)
|
39
49
|
|
40
50
|
next unless RubyCode.new(code:).valid?
|
41
51
|
|
42
|
-
|
43
|
-
|
52
|
+
FlogWrapper.new.examine(code).then do |examined|
|
53
|
+
stat[file][:average] = examined.average
|
54
|
+
stat[file][:total_score] = examined.average
|
44
55
|
end
|
45
56
|
|
46
57
|
stat[file][:smells] = ReekWrapper.new.examine(code).map(&:smell_type).tally
|
47
58
|
end
|
48
59
|
|
49
|
-
# return if smells.empty? && stat.empty?
|
50
|
-
|
51
60
|
stat = Stat.new(stat:)
|
52
61
|
|
53
|
-
AnalizedResult.new(label: "#{commit.time.iso8601} (#{commit.sha})",
|
62
|
+
AnalizedResult.new(label: "#{commit.time.iso8601} (#{commit.sha})",
|
63
|
+
commit:,
|
64
|
+
stat:)
|
54
65
|
end
|
55
66
|
end
|
56
67
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zubat'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
module Zubat
|
7
|
+
module CLI
|
8
|
+
class App
|
9
|
+
Argv = Data.define(:files, :silent, :root) do
|
10
|
+
def self.parse!(argv)
|
11
|
+
opt = OptionParser.new
|
12
|
+
|
13
|
+
root = nil
|
14
|
+
silent = false
|
15
|
+
|
16
|
+
opt.on('--silent', '-s') { silent = true }
|
17
|
+
opt.on('--root=ROOT') { |v| root = v }
|
18
|
+
|
19
|
+
files = opt.parse!(argv).uniq
|
20
|
+
|
21
|
+
abort 'no files to process, aborting.' if argv.empty?
|
22
|
+
|
23
|
+
new(files:, silent:, root:)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class GitRepo
|
28
|
+
URL_FORMATS = [
|
29
|
+
%r[https://(.+)/(.+)/(.+).git],
|
30
|
+
%r[git@(.+):(.+)/(.+).git],
|
31
|
+
%r[ssh://(.+)/(.+)/(.+)],
|
32
|
+
]
|
33
|
+
|
34
|
+
def self.guess
|
35
|
+
url = GitCommandWrapper.new.remote_origin_url
|
36
|
+
|
37
|
+
matched = URL_FORMATS
|
38
|
+
.find { |format| format.match?(url) }
|
39
|
+
&.match(url)
|
40
|
+
|
41
|
+
return unless matched
|
42
|
+
|
43
|
+
new(hostname: matched[1], org: matched[2], repo: matched[3])
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(hostname:, org:, repo:)
|
47
|
+
@hostname = hostname
|
48
|
+
@org = org
|
49
|
+
@repo = repo
|
50
|
+
end
|
51
|
+
|
52
|
+
def site_url
|
53
|
+
"https://#{@hostname}/#{@org}/#{@repo}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Progress
|
58
|
+
include Enumerable
|
59
|
+
|
60
|
+
def initialize(enum, silent:)
|
61
|
+
@enum = enum
|
62
|
+
@silent = silent
|
63
|
+
end
|
64
|
+
|
65
|
+
def each(&block)
|
66
|
+
@enum.each_with_index do |elem, i|
|
67
|
+
$stderr.print "\r#{%w[| / - \\][i % 4]} Analyzing... #{100 * i / @enum.size}%" unless @silent
|
68
|
+
|
69
|
+
block.call(elem)
|
70
|
+
end
|
71
|
+
|
72
|
+
warn "\r✨ Analized \n\n" unless @silent
|
73
|
+
|
74
|
+
@enum
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def start(argv)
|
79
|
+
argv = Argv.parse!(argv)
|
80
|
+
|
81
|
+
files = argv.files
|
82
|
+
|
83
|
+
repo = nil
|
84
|
+
|
85
|
+
results = Dir.chdir(argv.root || Dir.pwd) do
|
86
|
+
repo = GitRepo.guess
|
87
|
+
|
88
|
+
commits = Zubat::Commit.find(files:)
|
89
|
+
|
90
|
+
Progress
|
91
|
+
.new(commits, silent: argv.silent)
|
92
|
+
.map { |commit| Zubat::Analizer.new.analize(files:, commit:) }
|
93
|
+
end
|
94
|
+
|
95
|
+
file = Generator.new.generate(results:, site_url: repo&.site_url)
|
96
|
+
|
97
|
+
puts "Generated - #{file}\n"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/zubat/commit.rb
CHANGED
data/lib/zubat/generator.rb
CHANGED
@@ -4,8 +4,10 @@ module Zubat
|
|
4
4
|
class Generator
|
5
5
|
FILE = 'tmp/zubat/index.html'
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
TEMPLATE = 'templates/chart.html.erb'
|
8
|
+
|
9
|
+
def generate(results:, site_url:)
|
10
|
+
erb = Zubat.root.join(TEMPLATE).read
|
9
11
|
|
10
12
|
ylabels = []
|
11
13
|
|
@@ -33,14 +35,16 @@ module Zubat
|
|
33
35
|
|
34
36
|
dataset[:data] << {
|
35
37
|
label: xlabel,
|
38
|
+
commit_sha: result.commit.sha,
|
39
|
+
commit_diff: result.stat.diff(ylabel),
|
36
40
|
complexity_total: result.stat.complexity_total(ylabel),
|
37
41
|
complexity_average: result.stat.complexity_average(ylabel),
|
38
|
-
smells_scores: result.stat.smell_scores(ylabel).map { |type, value| { type:, value: } }
|
42
|
+
smells_scores: result.stat.smell_scores(ylabel).map { |type, value| { type:, value: } },
|
39
43
|
}
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
43
|
-
html = ERB.new(erb).result_with_hash(datasets:)
|
47
|
+
html = ERB.new(erb).result_with_hash(datasets:, site_url:)
|
44
48
|
|
45
49
|
file = File.expand_path(FILE)
|
46
50
|
|
@@ -7,6 +7,10 @@ require 'securerandom'
|
|
7
7
|
module Zubat
|
8
8
|
class GitCommandWrapper
|
9
9
|
module Stub
|
10
|
+
def remote_origin_url
|
11
|
+
"https://github.com/mizoR/zubat.git"
|
12
|
+
end
|
13
|
+
|
10
14
|
def log(files:)
|
11
15
|
logs = files.map do
|
12
16
|
Log.new(sha: SecureRandom.hex(3), time: Time.at(rand(1_900_000_000..1_900_999_999)))
|
@@ -32,6 +36,17 @@ module Zubat
|
|
32
36
|
end
|
33
37
|
SCRIPT
|
34
38
|
end
|
39
|
+
|
40
|
+
def diff(sha:, file:) # rubocop:disable Lint/UnusedMethodArgument
|
41
|
+
<<~SCRIPT
|
42
|
+
diff --git a/hello_world.rb b/hello_world.rb
|
43
|
+
class HelloWorld
|
44
|
+
def show
|
45
|
+
+ puts "Hello World"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
SCRIPT
|
49
|
+
end
|
35
50
|
end
|
36
51
|
|
37
52
|
Log = Data.define(:sha, :time)
|
@@ -42,6 +57,10 @@ module Zubat
|
|
42
57
|
instance
|
43
58
|
end
|
44
59
|
|
60
|
+
def remote_origin_url
|
61
|
+
`git config --get remote.origin.url`.chomp
|
62
|
+
end
|
63
|
+
|
45
64
|
def log(files:)
|
46
65
|
logs = `git log --oneline --pretty=format:'{ "sha": "%h", "time": "%ad" }' -- #{files.join(' ')}`.split("\n")
|
47
66
|
|
@@ -65,5 +84,9 @@ module Zubat
|
|
65
84
|
def show(sha:, file:)
|
66
85
|
`git show #{sha}:#{file}`
|
67
86
|
end
|
87
|
+
|
88
|
+
def diff(sha:, file:)
|
89
|
+
`git show #{sha} -- #{file}`
|
90
|
+
end
|
68
91
|
end
|
69
92
|
end
|
data/lib/zubat/version.rb
CHANGED
data/templates/chart.html.erb
CHANGED
@@ -4,6 +4,10 @@
|
|
4
4
|
<meta name="robots" content="noindex,nofollow">
|
5
5
|
<meta charset="UTF-8">
|
6
6
|
|
7
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" integrity="sha512-hasIneQUHlh06VNBe7f6ZcHmeRTLIaQWFd43YriJ0UND19bvYRauxthDg8E4eVNPm9bRUhr5JGeqH7FRFXQu5g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
8
|
+
|
9
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
10
|
+
|
7
11
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
|
8
12
|
|
9
13
|
<style>
|
@@ -253,6 +257,12 @@
|
|
253
257
|
left: 200px;
|
254
258
|
}
|
255
259
|
}
|
260
|
+
|
261
|
+
#diffArea {
|
262
|
+
max-height: 400px;
|
263
|
+
overflow: auto;
|
264
|
+
}
|
265
|
+
|
256
266
|
</style>
|
257
267
|
<title>zubat</title>
|
258
268
|
</head>
|
@@ -284,23 +294,17 @@
|
|
284
294
|
</div>
|
285
295
|
|
286
296
|
<div class="content">
|
297
|
+
<p><pre id="diffArea"></pre></p>
|
298
|
+
|
287
299
|
<h2 id="complexity_total" class="content-subhead">Complexity Total</h2>
|
288
|
-
<p>
|
289
|
-
<canvas id="chart_of_code_complexity_total_trend" width="400" height="300"></canvas>
|
290
|
-
</p>
|
300
|
+
<p><canvas id="chart_of_code_complexity_total_trend" width="400" height="200"></canvas></p>
|
291
301
|
|
292
302
|
<h2 id="complexity_average" class="content-subhead">Complexity Average <small>- Complexity score per methods</small></h2>
|
293
|
-
<p>
|
294
|
-
<canvas id="chart_of_code_complexity_average_trend" width="400" height="300"></canvas>
|
295
|
-
</p>
|
303
|
+
<p><canvas id="chart_of_code_complexity_average_trend" width="400" height="200"></canvas></p>
|
296
304
|
|
297
305
|
<h2 id="code_smells" class="content-subhead">Code smells</h2>
|
298
|
-
<p>
|
299
|
-
|
300
|
-
</p>
|
301
|
-
<p>
|
302
|
-
<canvas id="chart_of_code_smell_scores_trend" width="400" height="300"></canvas>
|
303
|
-
</p>
|
306
|
+
<p><div id="select_of_code_smell_scores_trend"></div></p>
|
307
|
+
<p><canvas id="chart_of_code_smell_scores_trend" width="400" height="200"></canvas></p>
|
304
308
|
</div>
|
305
309
|
</div>
|
306
310
|
</div>
|
@@ -308,11 +312,34 @@
|
|
308
312
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
309
313
|
|
310
314
|
<script>
|
315
|
+
var siteUrl = <%= site_url.to_json %>;
|
316
|
+
|
311
317
|
var datasets = <%= datasets.to_json %>;
|
312
318
|
|
319
|
+
var onClickWith = function (chart) {
|
320
|
+
return function (e) {
|
321
|
+
const points = chart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true);
|
322
|
+
|
323
|
+
if (points.length === 0) return;
|
324
|
+
|
325
|
+
const point = points[0];
|
326
|
+
const label = chart.data.labels[point.index];
|
327
|
+
const value = chart.data.datasets[point.datasetIndex].data[point.index];
|
328
|
+
|
329
|
+
const code = document.createElement('code');
|
330
|
+
code.classList.add('language-diff');
|
331
|
+
code.textContent = value.commit_diff;
|
332
|
+
hljs.highlightBlock(code);
|
333
|
+
|
334
|
+
const diffArea = document.getElementById('diffArea');
|
335
|
+
diffArea.innerHTML = '';
|
336
|
+
diffArea.appendChild(code);
|
337
|
+
};
|
338
|
+
};
|
339
|
+
|
313
340
|
var ctx0 = document.getElementById("chart_of_code_complexity_total_trend");
|
314
341
|
|
315
|
-
new Chart(ctx0, {
|
342
|
+
var chart0 = new Chart(ctx0, {
|
316
343
|
type: 'line',
|
317
344
|
data: {
|
318
345
|
datasets: datasets
|
@@ -333,9 +360,11 @@
|
|
333
360
|
}
|
334
361
|
});
|
335
362
|
|
363
|
+
ctx0.addEventListener('click', onClickWith(chart0));
|
364
|
+
|
336
365
|
var ctx1 = document.getElementById("chart_of_code_complexity_average_trend");
|
337
366
|
|
338
|
-
new Chart(ctx1, {
|
367
|
+
var chart1 = new Chart(ctx1, {
|
339
368
|
type: 'line',
|
340
369
|
data: {
|
341
370
|
datasets: datasets
|
@@ -356,6 +385,8 @@
|
|
356
385
|
}
|
357
386
|
});
|
358
387
|
|
388
|
+
ctx1.addEventListener('click', onClickWith(chart1));
|
389
|
+
|
359
390
|
var ctx2 = document.getElementById("chart_of_code_smell_scores_trend");
|
360
391
|
|
361
392
|
var select = document.createElement('select');
|
@@ -369,8 +400,6 @@
|
|
369
400
|
select.add(option);
|
370
401
|
});
|
371
402
|
|
372
|
-
|
373
|
-
|
374
403
|
select.addEventListener('change', function () {
|
375
404
|
var file = select.value;
|
376
405
|
|
@@ -379,7 +408,7 @@
|
|
379
408
|
|
380
409
|
document.getElementById("select_of_code_smell_scores_trend").appendChild(select);
|
381
410
|
|
382
|
-
var
|
411
|
+
var chart2 = null;
|
383
412
|
|
384
413
|
var showCodeSmellChartFor = function (file) {
|
385
414
|
var data = datasets.find(item => item.label === file).data;
|
@@ -400,9 +429,9 @@
|
|
400
429
|
};
|
401
430
|
});
|
402
431
|
|
403
|
-
if (
|
432
|
+
if (chart2) chart2.destroy();
|
404
433
|
|
405
|
-
|
434
|
+
chart2 = new Chart(ctx2, {
|
406
435
|
type: 'line',
|
407
436
|
data: {
|
408
437
|
datasets: smellsets
|
@@ -422,6 +451,8 @@
|
|
422
451
|
}
|
423
452
|
}
|
424
453
|
});
|
454
|
+
|
455
|
+
ctx2.addEventListener('click', onClickWith(chart2));
|
425
456
|
};
|
426
457
|
|
427
458
|
showCodeSmellChartFor(datasets[0].label);
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zubat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mizokami
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flog
|
@@ -54,7 +54,7 @@ files:
|
|
54
54
|
- exe/zubat
|
55
55
|
- lib/zubat.rb
|
56
56
|
- lib/zubat/analizer.rb
|
57
|
-
- lib/zubat/cli.rb
|
57
|
+
- lib/zubat/cli/app.rb
|
58
58
|
- lib/zubat/commit.rb
|
59
59
|
- lib/zubat/flog_wrapper.rb
|
60
60
|
- lib/zubat/generator.rb
|
data/lib/zubat/cli.rb
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'zubat'
|
4
|
-
require 'optparse'
|
5
|
-
|
6
|
-
module Zubat
|
7
|
-
class CLI
|
8
|
-
Argv = Data.define(:files, :silent, :root) do
|
9
|
-
def self.parse!(argv)
|
10
|
-
opt = OptionParser.new
|
11
|
-
|
12
|
-
root = nil
|
13
|
-
silent = false
|
14
|
-
|
15
|
-
opt.on('--silent', '-s') { silent = true }
|
16
|
-
opt.on('--root=ROOT') { |v| root = v }
|
17
|
-
|
18
|
-
files = opt.parse!(argv)
|
19
|
-
|
20
|
-
abort 'no files to process, aborting.' if files.empty? && argv.empty?
|
21
|
-
|
22
|
-
Dir.chdir(root || Dir.pwd) do
|
23
|
-
files = files.uniq
|
24
|
-
end
|
25
|
-
|
26
|
-
new(files:, silent:, root:)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Progress
|
31
|
-
include Enumerable
|
32
|
-
|
33
|
-
def initialize(enum, silent:)
|
34
|
-
@enum = enum
|
35
|
-
@silent = silent
|
36
|
-
end
|
37
|
-
|
38
|
-
def each(&block)
|
39
|
-
@enum.each_with_index do |elem, i|
|
40
|
-
$stderr.print "\r#{%w[| / - \\][i % 4]} Analyzing... #{100 * i / @enum.size}%" unless @silent
|
41
|
-
|
42
|
-
block.call(elem)
|
43
|
-
end
|
44
|
-
|
45
|
-
warn "\r✨ Analized \n\n" unless @silent
|
46
|
-
|
47
|
-
@enum
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def start(argv)
|
52
|
-
argv = Argv.parse!(argv)
|
53
|
-
|
54
|
-
generator = Generator.new
|
55
|
-
|
56
|
-
results = Dir.chdir(argv.root || Dir.pwd) do
|
57
|
-
files = argv.files
|
58
|
-
|
59
|
-
analizer = Zubat::Analizer.new
|
60
|
-
|
61
|
-
commits = Zubat::Commit.find(files:)
|
62
|
-
|
63
|
-
progress = Progress.new(commits, silent: argv.silent)
|
64
|
-
|
65
|
-
progress.map { |commit| analizer.analize(files:, commit:) }
|
66
|
-
end
|
67
|
-
|
68
|
-
file = generator.generate(results:)
|
69
|
-
|
70
|
-
puts "Generated - #{file}\n"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|