zubat 0.0.1 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f4eb5790313a7a672cad41fcf05c8c5515659f265a60952efbf3bb28fad7fb1
4
- data.tar.gz: 0b94a1a09d207f79c9a32414655c4ec9706f47323659fc64d7f433f60ded012b
3
+ metadata.gz: baa42361d5aa2f8268e3d5be1e21cc2e0ae7e6b1ce839b43fe9b410f8678e92c
4
+ data.tar.gz: da15e758d143aa4ae0521d2281577e33cbd08dccd209de20e116068846bc715c
5
5
  SHA512:
6
- metadata.gz: 52c4ba88c4e15d3e947c0022b15f3da7477f7e8d5d7fbda9a0b43d7e8e61e13da446c59b7b6e54f425e32c31bf008bdbe1e3f19021f25c3c9ce728630e4eb586
7
- data.tar.gz: 34b2ad121d67f463e93f1cb19153dba16764e33b59e73aef0a7e7704610b695d6a5320fd8a19bb4108787973aa19cc4ad1a3a2b56aa6e4e76835df034de8138c
6
+ metadata.gz: 824f35f0dbaa6fa6ddb9cb37bf47f2f8d3221c393beae470465a979fa6773a807c07a305dfd737e81c53935c7f6cb6f7daba525c1204243803d8d36096656625
7
+ data.tar.gz: abd8a26623ed6a4d60bec503551463de0c9cbb7e551b57e6c69702d37280f616fd2158cecfe78906252de3db40ccc6b0f2d8aeeb28154a25e67ea78e061c7033
data/exe/zubat CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'zubat/cli'
4
+ require 'zubat/cli/app'
5
5
 
6
- Zubat::CLI.new.start(ARGV)
6
+ Zubat::CLI::App.new.start(ARGV)
@@ -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
- next unless commit.exists?(file:)
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
- stat[file] = FlogWrapper.new.examine(code).then do |examined|
43
- { average: examined.average, total_score: examined.total_score }
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})", stat:)
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
@@ -26,5 +26,9 @@ module Zubat
26
26
  def show(file:)
27
27
  GitCommandWrapper.new.show(sha: @sha, file:)
28
28
  end
29
+
30
+ def diff(file:)
31
+ GitCommandWrapper.new.diff(sha: @sha, file:)
32
+ end
29
33
  end
30
34
  end
@@ -4,8 +4,10 @@ module Zubat
4
4
  class Generator
5
5
  FILE = 'tmp/zubat/index.html'
6
6
 
7
- def generate(results:)
8
- erb = Zubat.root.join('templates/chart.html.erb').read
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zubat
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.3'
5
5
  end
@@ -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
- <div id="select_of_code_smell_scores_trend"></div>
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 chart3 = null;
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 (chart3) chart3.destroy();
432
+ if (chart2) chart2.destroy();
404
433
 
405
- chart3 = new Chart(ctx2, {
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.1
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-08 00:00:00.000000000 Z
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