zubat 0.0.1
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +12 -0
- data/exe/zubat +6 -0
- data/lib/zubat/analizer.rb +56 -0
- data/lib/zubat/cli.rb +73 -0
- data/lib/zubat/commit.rb +30 -0
- data/lib/zubat/flog_wrapper.rb +25 -0
- data/lib/zubat/generator.rb +54 -0
- data/lib/zubat/git_command_wrapper.rb +69 -0
- data/lib/zubat/reek_wrapper.rb +18 -0
- data/lib/zubat/ruby_code.rb +17 -0
- data/lib/zubat/version.rb +5 -0
- data/lib/zubat.rb +26 -0
- data/screenshot.png +0 -0
- data/sig/zubat.rbs +4 -0
- data/templates/chart.html.erb +430 -0
- metadata +94 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 3f4eb5790313a7a672cad41fcf05c8c5515659f265a60952efbf3bb28fad7fb1
         | 
| 4 | 
            +
              data.tar.gz: 0b94a1a09d207f79c9a32414655c4ec9706f47323659fc64d7f433f60ded012b
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 52c4ba88c4e15d3e947c0022b15f3da7477f7e8d5d7fbda9a0b43d7e8e61e13da446c59b7b6e54f425e32c31bf008bdbe1e3f19021f25c3c9ce728630e4eb586
         | 
| 7 | 
            +
              data.tar.gz: 34b2ad121d67f463e93f1cb19153dba16764e33b59e73aef0a7e7704610b695d6a5320fd8a19bb4108787973aa19cc4ad1a3a2b56aa6e4e76835df034de8138c
         | 
    
        data/.rspec
    ADDED
    
    
    
        data/.rubocop.yml
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2023 mizokami
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            # zubat🦇
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            zubat is a gem that wraps around static analysis gems [Reek](https://github.com/troessner/reek) and [Flog](https://github.com/seattlerb/flog) to visualize trends' report of your Ruby code quality.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Install the gem and add to the application's Gemfile by executing:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                $ bundle add zubat
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            If bundler is not being used to manage dependencies, install the gem by executing:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                $ gem install zubat
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ## Usage
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ```sh
         | 
| 18 | 
            +
            $ zubat path/to/awesome.rb
         | 
| 19 | 
            +
            ```
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            There are a few options
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ```sh
         | 
| 24 | 
            +
            Usage: zubat [OPTIONS] FILE [FILE]...
         | 
| 25 | 
            +
                -s, --silent
         | 
| 26 | 
            +
                    --root=ROOT
         | 
| 27 | 
            +
            ```
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            ## Screenshot
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ```sh
         | 
| 32 | 
            +
            # For example, runs the following command under the ruby project root. It reports the result as shown in the screenshot below.
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            $ zubat lib/find.rb lib/csv.rb lib/uri.rb lib/set.rb
         | 
| 35 | 
            +
            ```
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            ## Development
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            ## Contributing
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/mizoR/zubat.
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            ## License
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/exe/zubat
    ADDED
    
    
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Zubat
         | 
| 4 | 
            +
              class Analizer
         | 
| 5 | 
            +
                AnalizedResult = Data.define(:label, :stat)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # @param stat [Hash<String, Hash<String, Integer>>]
         | 
| 8 | 
            +
                Stat = Data.define(:stat) do
         | 
| 9 | 
            +
                  # @return [Float]
         | 
| 10 | 
            +
                  def complexity_average(file)
         | 
| 11 | 
            +
                    stat.fetch(file, {}).fetch(:average, nil)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # @return [Float]
         | 
| 15 | 
            +
                  def complexity_total(file)
         | 
| 16 | 
            +
                    stat.fetch(file, {}).fetch(:total_score, nil)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def smell_scores(file)
         | 
| 20 | 
            +
                    stat.fetch(file, {}).fetch(:smells, {})
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def smell_score(file, label)
         | 
| 24 | 
            +
                    stat.fetch(file).fetch(:smells).fetch(label, 0)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def each(*, **, &block)
         | 
| 28 | 
            +
                    stat.each(*, **, &block)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def analize(files:, commit:)
         | 
| 33 | 
            +
                  stat = {}
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  files.each do |file|
         | 
| 36 | 
            +
                    next unless commit.exists?(file:)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    code = commit.show(file:)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    next unless RubyCode.new(code:).valid?
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    stat[file] = FlogWrapper.new.examine(code).then do |examined|
         | 
| 43 | 
            +
                      { average: examined.average, total_score: examined.total_score }
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    stat[file][:smells] = ReekWrapper.new.examine(code).map(&:smell_type).tally
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  # return if smells.empty? && stat.empty?
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  stat = Stat.new(stat:)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  AnalizedResult.new(label: "#{commit.time.iso8601} (#{commit.sha})", stat:)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/lib/zubat/cli.rb
    ADDED
    
    | @@ -0,0 +1,73 @@ | |
| 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
         | 
    
        data/lib/zubat/commit.rb
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Zubat
         | 
| 4 | 
            +
              class Commit
         | 
| 5 | 
            +
                attr_reader :time, :sha
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def self.find(files:)
         | 
| 8 | 
            +
                  logs = GitCommandWrapper.new.log(files:)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  commits = logs.map { |log| Zubat::Commit.new(sha: log.sha, time: log.time) }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  commits.reverse!
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  commits
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def initialize(sha:, time:)
         | 
| 18 | 
            +
                  @sha = sha
         | 
| 19 | 
            +
                  @time = time
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def exists?(file:)
         | 
| 23 | 
            +
                  GitCommandWrapper.new.exists?(sha: @sha, file:)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def show(file:)
         | 
| 27 | 
            +
                  GitCommandWrapper.new.show(sha: @sha, file:)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'tempfile'
         | 
| 5 | 
            +
            require 'flog'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Zubat
         | 
| 8 | 
            +
              class FlogWrapper
         | 
| 9 | 
            +
                Flog = Data.define(:average, :total_score)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def examine(code)
         | 
| 12 | 
            +
                  flog = ::Flog.new
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  Tempfile.open do |file|
         | 
| 15 | 
            +
                    file.print(code)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    file.close
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    flog.flog(file.path)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    Flog.new(average: flog.average, total_score: flog.total_score)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Zubat
         | 
| 4 | 
            +
              class Generator
         | 
| 5 | 
            +
                FILE = 'tmp/zubat/index.html'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def generate(results:)
         | 
| 8 | 
            +
                  erb = Zubat.root.join('templates/chart.html.erb').read
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  ylabels = []
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  results.each do |result|
         | 
| 13 | 
            +
                    result.stat.each do |ylabel, _|
         | 
| 14 | 
            +
                      ylabels << ylabel unless ylabels.include?(ylabel)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  ylabels.sort!
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  datasets = []
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  results.each do |result|
         | 
| 23 | 
            +
                    xlabel = result.label
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    ylabels.each do |ylabel|
         | 
| 26 | 
            +
                      dataset = datasets.find { |ds| ds[:label] == ylabel }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      unless dataset
         | 
| 29 | 
            +
                        dataset = { label: ylabel, data: [] }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                        datasets << dataset
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      dataset[:data] << {
         | 
| 35 | 
            +
                        label: xlabel,
         | 
| 36 | 
            +
                        complexity_total: result.stat.complexity_total(ylabel),
         | 
| 37 | 
            +
                        complexity_average: result.stat.complexity_average(ylabel),
         | 
| 38 | 
            +
                        smells_scores: result.stat.smell_scores(ylabel).map { |type, value| { type:, value: } }
         | 
| 39 | 
            +
                      }
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  html = ERB.new(erb).result_with_hash(datasets:)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  file = File.expand_path(FILE)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  FileUtils.mkdir_p(File.dirname(file))
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  IO.write(file, html)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  file
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'json'
         | 
| 4 | 
            +
            require 'time'
         | 
| 5 | 
            +
            require 'securerandom'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Zubat
         | 
| 8 | 
            +
              class GitCommandWrapper
         | 
| 9 | 
            +
                module Stub
         | 
| 10 | 
            +
                  def log(files:)
         | 
| 11 | 
            +
                    logs = files.map do
         | 
| 12 | 
            +
                      Log.new(sha: SecureRandom.hex(3), time: Time.at(rand(1_900_000_000..1_900_999_999)))
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    logs.sort_by!(&:time)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    logs.reverse!
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    logs
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def exists?(sha:, file:) # rubocop:disable Lint/UnusedMethodArgument
         | 
| 23 | 
            +
                    true
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def show(sha:, file:) # rubocop:disable Lint/UnusedMethodArgument
         | 
| 27 | 
            +
                    <<~SCRIPT
         | 
| 28 | 
            +
                      class HelloWorld
         | 
| 29 | 
            +
                        def show
         | 
| 30 | 
            +
                          puts "Hello World"
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    SCRIPT
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                Log = Data.define(:sha, :time)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def self.new
         | 
| 40 | 
            +
                  instance = super
         | 
| 41 | 
            +
                  instance.extend(Stub) if Zubat.stubbed?
         | 
| 42 | 
            +
                  instance
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def log(files:)
         | 
| 46 | 
            +
                  logs = `git log --oneline --pretty=format:'{ "sha": "%h", "time": "%ad" }' -- #{files.join(' ')}`.split("\n")
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  logs.map! do |log|
         | 
| 49 | 
            +
                    args = JSON.parse(log, symbolize_names: true)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    Log.new(sha: args[:sha], time: Time.parse(args[:time]))
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  logs.sort_by!(&:time)
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  logs.reverse!
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  logs
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def exists?(sha:, file:)
         | 
| 62 | 
            +
                  !`git ls-tree -r #{sha} -- #{file} 2>/dev/null`.empty?
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def show(sha:, file:)
         | 
| 66 | 
            +
                  `git show #{sha}:#{file}`
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'json'
         | 
| 5 | 
            +
            require 'time'
         | 
| 6 | 
            +
            require 'reek'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Zubat
         | 
| 9 | 
            +
              class ReekWrapper
         | 
| 10 | 
            +
                Smell = Data.define(:smell_type)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def examine(code)
         | 
| 13 | 
            +
                  smells = Reek::Examiner.new(String.new(code)).smells
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  smells.map { |smell| Smell.new(smell_type: smell.smell_type) }
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        data/lib/zubat.rb
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'zubat/analizer'
         | 
| 4 | 
            +
            require_relative 'zubat/commit'
         | 
| 5 | 
            +
            require_relative 'zubat/flog_wrapper'
         | 
| 6 | 
            +
            require_relative 'zubat/generator'
         | 
| 7 | 
            +
            require_relative 'zubat/git_command_wrapper'
         | 
| 8 | 
            +
            require_relative 'zubat/reek_wrapper'
         | 
| 9 | 
            +
            require_relative 'zubat/ruby_code'
         | 
| 10 | 
            +
            require_relative 'zubat/version'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module Zubat
         | 
| 13 | 
            +
              class Error < StandardError; end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def self.stubbed=(stubbed)
         | 
| 16 | 
            +
                @stubbed = stubbed
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def self.stubbed?
         | 
| 20 | 
            +
                !!@stubbed
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def self.root
         | 
| 24 | 
            +
                Pathname.new("#{__dir__}/..")
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
    
        data/screenshot.png
    ADDED
    
    | Binary file | 
    
        data/sig/zubat.rbs
    ADDED
    
    
| @@ -0,0 +1,430 @@ | |
| 1 | 
            +
            <!DOCTYPE html>
         | 
| 2 | 
            +
            <html lang="en">
         | 
| 3 | 
            +
              <head>
         | 
| 4 | 
            +
                <meta name="robots" content="noindex,nofollow">
         | 
| 5 | 
            +
                <meta charset="UTF-8">
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                <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 | 
            +
             | 
| 9 | 
            +
                <style>
         | 
| 10 | 
            +
                body {
         | 
| 11 | 
            +
                  color: #777;
         | 
| 12 | 
            +
                }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                .pure-img-responsive {
         | 
| 15 | 
            +
                  max-width: 100%;
         | 
| 16 | 
            +
                  height: auto;
         | 
| 17 | 
            +
                }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                /*
         | 
| 20 | 
            +
                   Add transition to containers so they can push in and out.
         | 
| 21 | 
            +
                 */
         | 
| 22 | 
            +
                #layout,
         | 
| 23 | 
            +
                #menu,
         | 
| 24 | 
            +
                .menu-link {
         | 
| 25 | 
            +
                  -webkit-transition: all 0.2s ease-out;
         | 
| 26 | 
            +
                  -moz-transition: all 0.2s ease-out;
         | 
| 27 | 
            +
                  -ms-transition: all 0.2s ease-out;
         | 
| 28 | 
            +
                  -o-transition: all 0.2s ease-out;
         | 
| 29 | 
            +
                  transition: all 0.2s ease-out;
         | 
| 30 | 
            +
                }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                /*
         | 
| 33 | 
            +
                   This is the parent `<div>` that contains the menu and the content area.
         | 
| 34 | 
            +
                 */
         | 
| 35 | 
            +
                #layout {
         | 
| 36 | 
            +
                  position: relative;
         | 
| 37 | 
            +
                  left: 0;
         | 
| 38 | 
            +
                  padding-left: 0;
         | 
| 39 | 
            +
                }
         | 
| 40 | 
            +
                #layout.active #menu {
         | 
| 41 | 
            +
                  left: 200px;
         | 
| 42 | 
            +
                  width: 200px;
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                #layout.active .menu-link {
         | 
| 46 | 
            +
                  left: 200px;
         | 
| 47 | 
            +
                }
         | 
| 48 | 
            +
                /*
         | 
| 49 | 
            +
                   The content `<div>` is where all your content goes.
         | 
| 50 | 
            +
                 */
         | 
| 51 | 
            +
                .content {
         | 
| 52 | 
            +
                  margin: 0 auto;
         | 
| 53 | 
            +
                  padding: 0 2em;
         | 
| 54 | 
            +
                  max-width: 1200px;
         | 
| 55 | 
            +
                  margin-bottom: 50px;
         | 
| 56 | 
            +
                  line-height: 1.6em;
         | 
| 57 | 
            +
                }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                .header {
         | 
| 60 | 
            +
                  margin: 0;
         | 
| 61 | 
            +
                  color: #333;
         | 
| 62 | 
            +
                  text-align: center;
         | 
| 63 | 
            +
                  padding: 2.5em 2em 0;
         | 
| 64 | 
            +
                  border-bottom: 1px solid #eee;
         | 
| 65 | 
            +
                }
         | 
| 66 | 
            +
                .header h1 {
         | 
| 67 | 
            +
                  margin: 0.2em 0;
         | 
| 68 | 
            +
                  font-size: 3em;
         | 
| 69 | 
            +
                  font-weight: 300;
         | 
| 70 | 
            +
                }
         | 
| 71 | 
            +
                .header h2 {
         | 
| 72 | 
            +
                  font-weight: 300;
         | 
| 73 | 
            +
                  color: #ccc;
         | 
| 74 | 
            +
                  padding: 0;
         | 
| 75 | 
            +
                  margin-top: 0;
         | 
| 76 | 
            +
                }
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                .content-subhead {
         | 
| 79 | 
            +
                  margin: 50px 0 20px 0;
         | 
| 80 | 
            +
                  font-weight: 300;
         | 
| 81 | 
            +
                  color: #888;
         | 
| 82 | 
            +
                }
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                /*
         | 
| 85 | 
            +
                   The `#menu` `<div>` is the parent `<div>` that contains the `.pure-menu` that
         | 
| 86 | 
            +
                     appears on the left side of the page.
         | 
| 87 | 
            +
                 */
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                #menu {
         | 
| 90 | 
            +
                  margin-left: -200px; /* "#menu" width */
         | 
| 91 | 
            +
                  width: 200px;
         | 
| 92 | 
            +
                  position: fixed;
         | 
| 93 | 
            +
                  top: 0;
         | 
| 94 | 
            +
                  left: 0;
         | 
| 95 | 
            +
                  bottom: 0;
         | 
| 96 | 
            +
                  z-index: 1000; /* so the menu or its navicon stays above all content */
         | 
| 97 | 
            +
                  background: #191818;
         | 
| 98 | 
            +
                  overflow-y: auto;
         | 
| 99 | 
            +
                }
         | 
| 100 | 
            +
                /*
         | 
| 101 | 
            +
                   All anchors inside the menu should be styled like this.
         | 
| 102 | 
            +
                 */
         | 
| 103 | 
            +
                #menu a {
         | 
| 104 | 
            +
                  color: #999;
         | 
| 105 | 
            +
                  border: none;
         | 
| 106 | 
            +
                  padding: 0.6em 0 0.6em 0.6em;
         | 
| 107 | 
            +
                }
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                /*
         | 
| 110 | 
            +
                   Remove all background/borders, since we are applying them to #menu.
         | 
| 111 | 
            +
                 */
         | 
| 112 | 
            +
                #menu .pure-menu,
         | 
| 113 | 
            +
                #menu .pure-menu ul {
         | 
| 114 | 
            +
                  border: none;
         | 
| 115 | 
            +
                  background: transparent;
         | 
| 116 | 
            +
                }
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                /*
         | 
| 119 | 
            +
                   Add that light border to separate items into groups.
         | 
| 120 | 
            +
                 */
         | 
| 121 | 
            +
                #menu .pure-menu ul,
         | 
| 122 | 
            +
                #menu .pure-menu .menu-item-divided {
         | 
| 123 | 
            +
                  border-top: 1px solid #333;
         | 
| 124 | 
            +
                }
         | 
| 125 | 
            +
                /*
         | 
| 126 | 
            +
                   Change color of the anchor links on hover/focus.
         | 
| 127 | 
            +
                 */
         | 
| 128 | 
            +
                #menu .pure-menu li a:hover,
         | 
| 129 | 
            +
                #menu .pure-menu li a:focus {
         | 
| 130 | 
            +
                  background: #333;
         | 
| 131 | 
            +
                }
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                /*
         | 
| 134 | 
            +
                   This styles the selected menu item `<li>`.
         | 
| 135 | 
            +
                 */
         | 
| 136 | 
            +
                #menu .pure-menu-selected,
         | 
| 137 | 
            +
                #menu .pure-menu-heading {
         | 
| 138 | 
            +
                  background: #1f8dd6;
         | 
| 139 | 
            +
                }
         | 
| 140 | 
            +
                /*
         | 
| 141 | 
            +
                   This styles a link within a selected menu item `<li>`.
         | 
| 142 | 
            +
                 */
         | 
| 143 | 
            +
                #menu .pure-menu-selected a {
         | 
| 144 | 
            +
                  color: #fff;
         | 
| 145 | 
            +
                }
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                /*
         | 
| 148 | 
            +
                   This styles the menu heading.
         | 
| 149 | 
            +
                 */
         | 
| 150 | 
            +
                #menu .pure-menu-heading {
         | 
| 151 | 
            +
                  font-size: 110%;
         | 
| 152 | 
            +
                  color: #fff;
         | 
| 153 | 
            +
                  margin: 0;
         | 
| 154 | 
            +
                }
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                /* -- Dynamic Button For Responsive Menu -------------------------------------*/
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                /*
         | 
| 159 | 
            +
                   The button to open/close the Menu is custom-made and not part of Pure. Here's
         | 
| 160 | 
            +
                     how it works:
         | 
| 161 | 
            +
                 */
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                /*
         | 
| 164 | 
            +
                   `.menu-link` represents the responsive menu toggle that shows/hides on
         | 
| 165 | 
            +
                     small screens.
         | 
| 166 | 
            +
                 */
         | 
| 167 | 
            +
                .menu-link {
         | 
| 168 | 
            +
                  position: fixed;
         | 
| 169 | 
            +
                  display: block; /* show this only on small screens */
         | 
| 170 | 
            +
                  top: 0;
         | 
| 171 | 
            +
                  left: 0; /* "#menu width" */
         | 
| 172 | 
            +
                  background: #000;
         | 
| 173 | 
            +
                  background: rgba(0,0,0,0.7);
         | 
| 174 | 
            +
                  font-size: 10px; /* change this value to increase/decrease button size */
         | 
| 175 | 
            +
                  z-index: 10;
         | 
| 176 | 
            +
                  width: 2em;
         | 
| 177 | 
            +
                  height: auto;
         | 
| 178 | 
            +
                  padding: 2.1em 1.6em;
         | 
| 179 | 
            +
                }
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                .menu-link:hover,
         | 
| 182 | 
            +
                .menu-link:focus {
         | 
| 183 | 
            +
                  background: #000;
         | 
| 184 | 
            +
                }
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                .menu-link span {
         | 
| 187 | 
            +
                  position: relative;
         | 
| 188 | 
            +
                  display: block;
         | 
| 189 | 
            +
                }
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                .menu-link span,
         | 
| 192 | 
            +
                .menu-link span:before,
         | 
| 193 | 
            +
                .menu-link span:after {
         | 
| 194 | 
            +
                  background-color: #fff;
         | 
| 195 | 
            +
                  pointer-events: none;
         | 
| 196 | 
            +
                  width: 100%;
         | 
| 197 | 
            +
                  height: 0.2em;
         | 
| 198 | 
            +
                }
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                .menu-link span:before,
         | 
| 201 | 
            +
                .menu-link span:after {
         | 
| 202 | 
            +
                  position: absolute;
         | 
| 203 | 
            +
                  margin-top: -0.6em;
         | 
| 204 | 
            +
                  content: " ";
         | 
| 205 | 
            +
                }
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                .menu-link span:after {
         | 
| 208 | 
            +
                  margin-top: 0.6em;
         | 
| 209 | 
            +
                }
         | 
| 210 | 
            +
             | 
| 211 | 
            +
             | 
| 212 | 
            +
                /* -- Responsive Styles (Media Queries) ------------------------------------- */
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                /*
         | 
| 215 | 
            +
                   Hides the menu at `48em`, but modify this based on your app's needs.
         | 
| 216 | 
            +
                 */
         | 
| 217 | 
            +
                @media (min-width: 48em) {
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  .header,
         | 
| 220 | 
            +
                  .content {
         | 
| 221 | 
            +
                    padding-left: 2em;
         | 
| 222 | 
            +
                    padding-right: 2em;
         | 
| 223 | 
            +
                  }
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                  #layout {
         | 
| 226 | 
            +
                    padding-left: 200px; /* left col width "#menu" */
         | 
| 227 | 
            +
                    left: 0;
         | 
| 228 | 
            +
                  }
         | 
| 229 | 
            +
                  #menu {
         | 
| 230 | 
            +
                    left: 200px;
         | 
| 231 | 
            +
                  }
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  .menu-link {
         | 
| 234 | 
            +
                    position: fixed;
         | 
| 235 | 
            +
                    left: 200px;
         | 
| 236 | 
            +
                    display: none;
         | 
| 237 | 
            +
                  }
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                  #layout.active .menu-link {
         | 
| 240 | 
            +
                    left: 200px;
         | 
| 241 | 
            +
                  }
         | 
| 242 | 
            +
                }
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                @media (max-width: 48em) {
         | 
| 245 | 
            +
                  /* Only apply this when the window is small. Otherwise, the following
         | 
| 246 | 
            +
                     case results in extra padding on the left:
         | 
| 247 | 
            +
                   * Make the window small.
         | 
| 248 | 
            +
                   * Tap the menu to trigger the active state.
         | 
| 249 | 
            +
                   * Make the window large again.
         | 
| 250 | 
            +
                   */
         | 
| 251 | 
            +
                  #layout.active {
         | 
| 252 | 
            +
                    position: relative;
         | 
| 253 | 
            +
                    left: 200px;
         | 
| 254 | 
            +
                  }
         | 
| 255 | 
            +
                }
         | 
| 256 | 
            +
                </style>
         | 
| 257 | 
            +
                <title>zubat</title>
         | 
| 258 | 
            +
              </head>
         | 
| 259 | 
            +
             | 
| 260 | 
            +
              <body>
         | 
| 261 | 
            +
                <div id="layout">
         | 
| 262 | 
            +
                  <!-- Menu toggle -->
         | 
| 263 | 
            +
                  <a href="#menu" id="menuLink" class="menu-link">
         | 
| 264 | 
            +
                    <!-- Hamburger icon -->
         | 
| 265 | 
            +
                    <span></span>
         | 
| 266 | 
            +
                  </a>
         | 
| 267 | 
            +
             | 
| 268 | 
            +
                  <div id="menu">
         | 
| 269 | 
            +
                    <div class="pure-menu">
         | 
| 270 | 
            +
                      <a class="pure-menu-heading" href="#main">zubat</a>
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                      <ul class="pure-menu-list">
         | 
| 273 | 
            +
                        <li class="pure-menu-item"><a href="#complexity_total" class="pure-menu-link">Complexity Total</a></li>
         | 
| 274 | 
            +
                        <li class="pure-menu-item"><a href="#complexity_average" class="pure-menu-link">Code Average</a></li>
         | 
| 275 | 
            +
                        <li class="pure-menu-item"><a href="#code_smells" class="pure-menu-link">Code smells</a></li>
         | 
| 276 | 
            +
                      </ul>
         | 
| 277 | 
            +
                    </div>
         | 
| 278 | 
            +
                  </div>
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                  <div id="main">
         | 
| 281 | 
            +
                    <div class="header">
         | 
| 282 | 
            +
                      <h1>zubat</h1>
         | 
| 283 | 
            +
                      <h2>Visualize trends of your code complexity and smells</h2>
         | 
| 284 | 
            +
                    </div>
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                    <div class="content">
         | 
| 287 | 
            +
                      <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>
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                      <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>
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                      <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>
         | 
| 304 | 
            +
                    </div>
         | 
| 305 | 
            +
                  </div>
         | 
| 306 | 
            +
                </div>
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                <script>
         | 
| 311 | 
            +
                  var datasets =  <%= datasets.to_json %>;
         | 
| 312 | 
            +
             | 
| 313 | 
            +
                  var ctx0 = document.getElementById("chart_of_code_complexity_total_trend");
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                  new Chart(ctx0, {
         | 
| 316 | 
            +
                    type: 'line',
         | 
| 317 | 
            +
                    data: {
         | 
| 318 | 
            +
                      datasets: datasets
         | 
| 319 | 
            +
                    },
         | 
| 320 | 
            +
                    options: {
         | 
| 321 | 
            +
                      parsing: {
         | 
| 322 | 
            +
                        xAxisKey: 'label',
         | 
| 323 | 
            +
                        yAxisKey: 'complexity_total'
         | 
| 324 | 
            +
                      },
         | 
| 325 | 
            +
                      fill: true,
         | 
| 326 | 
            +
                      indexAxis: 'x',
         | 
| 327 | 
            +
                      scales: {
         | 
| 328 | 
            +
                        y: {
         | 
| 329 | 
            +
                          beginAtZero: true,
         | 
| 330 | 
            +
                          stacked: false
         | 
| 331 | 
            +
                        }
         | 
| 332 | 
            +
                      }
         | 
| 333 | 
            +
                    }
         | 
| 334 | 
            +
                  });
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                  var ctx1 = document.getElementById("chart_of_code_complexity_average_trend");
         | 
| 337 | 
            +
             | 
| 338 | 
            +
                  new Chart(ctx1, {
         | 
| 339 | 
            +
                    type: 'line',
         | 
| 340 | 
            +
                    data: {
         | 
| 341 | 
            +
                      datasets: datasets
         | 
| 342 | 
            +
                    },
         | 
| 343 | 
            +
                    options: {
         | 
| 344 | 
            +
                      parsing: {
         | 
| 345 | 
            +
                        xAxisKey: 'label',
         | 
| 346 | 
            +
                        yAxisKey: 'complexity_average'
         | 
| 347 | 
            +
                      },
         | 
| 348 | 
            +
                      fill: true,
         | 
| 349 | 
            +
                      indexAxis: 'x',
         | 
| 350 | 
            +
                      scales: {
         | 
| 351 | 
            +
                        y: {
         | 
| 352 | 
            +
                          beginAtZero: true,
         | 
| 353 | 
            +
                          stacked: false
         | 
| 354 | 
            +
                        }
         | 
| 355 | 
            +
                      }
         | 
| 356 | 
            +
                    }
         | 
| 357 | 
            +
                  });
         | 
| 358 | 
            +
             | 
| 359 | 
            +
                  var ctx2 = document.getElementById("chart_of_code_smell_scores_trend");
         | 
| 360 | 
            +
             | 
| 361 | 
            +
                  var select = document.createElement('select');
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                  datasets.map(item => {
         | 
| 364 | 
            +
                    let option = document.createElement('option');
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                    option.text = item.label;
         | 
| 367 | 
            +
                    option.value = item.label;
         | 
| 368 | 
            +
             | 
| 369 | 
            +
                    select.add(option);
         | 
| 370 | 
            +
                  });
         | 
| 371 | 
            +
             | 
| 372 | 
            +
             | 
| 373 | 
            +
             | 
| 374 | 
            +
                  select.addEventListener('change', function () {
         | 
| 375 | 
            +
                    var file = select.value;
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                    showCodeSmellChartFor(file);
         | 
| 378 | 
            +
                  });
         | 
| 379 | 
            +
             | 
| 380 | 
            +
                  document.getElementById("select_of_code_smell_scores_trend").appendChild(select);
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                  var chart3 = null;
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                  var showCodeSmellChartFor = function (file) {
         | 
| 385 | 
            +
                    var data = datasets.find(item => item.label === file).data;
         | 
| 386 | 
            +
             | 
| 387 | 
            +
                    var smellTypes = Array.from(new Set(data.flatMap(item => item.smells_scores).map(item => item.type))).sort();
         | 
| 388 | 
            +
             | 
| 389 | 
            +
                    var smellsets = smellTypes.map(smellType => {
         | 
| 390 | 
            +
                      return {
         | 
| 391 | 
            +
                        label: smellType,
         | 
| 392 | 
            +
                        data: data.map(item => {
         | 
| 393 | 
            +
                          var value = 0;
         | 
| 394 | 
            +
                          var smellScore = item.smells_scores.find(smellScore => smellScore.type === smellType);
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                          if (smellScore) value = smellScore.value;
         | 
| 397 | 
            +
             | 
| 398 | 
            +
                          return { label: item.label, value: value };
         | 
| 399 | 
            +
                        })
         | 
| 400 | 
            +
                      };
         | 
| 401 | 
            +
                    });
         | 
| 402 | 
            +
             | 
| 403 | 
            +
                    if (chart3) chart3.destroy();
         | 
| 404 | 
            +
             | 
| 405 | 
            +
                    chart3 = new Chart(ctx2, {
         | 
| 406 | 
            +
                      type: 'line',
         | 
| 407 | 
            +
                      data: {
         | 
| 408 | 
            +
                        datasets: smellsets
         | 
| 409 | 
            +
                      },
         | 
| 410 | 
            +
                      options: {
         | 
| 411 | 
            +
                        parsing: {
         | 
| 412 | 
            +
                          xAxisKey: 'label',
         | 
| 413 | 
            +
                          yAxisKey: 'value'
         | 
| 414 | 
            +
                        },
         | 
| 415 | 
            +
                        fill: true,
         | 
| 416 | 
            +
                        indexAxis: 'x',
         | 
| 417 | 
            +
                        scales: {
         | 
| 418 | 
            +
                          y: {
         | 
| 419 | 
            +
                            beginAtZero: true,
         | 
| 420 | 
            +
                            stacked: true
         | 
| 421 | 
            +
                          }
         | 
| 422 | 
            +
                        }
         | 
| 423 | 
            +
                      }
         | 
| 424 | 
            +
                    });
         | 
| 425 | 
            +
                  };
         | 
| 426 | 
            +
             | 
| 427 | 
            +
                  showCodeSmellChartFor(datasets[0].label);
         | 
| 428 | 
            +
                </script>
         | 
| 429 | 
            +
              </body>
         | 
| 430 | 
            +
            </html>
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: zubat
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - mizokami
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2023-11-08 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: flog
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '4.8'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '4.8'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: reek
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '6.1'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '6.1'
         | 
| 41 | 
            +
            description: Visualize trends of your code complexity and smells
         | 
| 42 | 
            +
            email:
         | 
| 43 | 
            +
            - r.mizokami@gmail.com
         | 
| 44 | 
            +
            executables:
         | 
| 45 | 
            +
            - zubat
         | 
| 46 | 
            +
            extensions: []
         | 
| 47 | 
            +
            extra_rdoc_files: []
         | 
| 48 | 
            +
            files:
         | 
| 49 | 
            +
            - ".rspec"
         | 
| 50 | 
            +
            - ".rubocop.yml"
         | 
| 51 | 
            +
            - LICENSE.txt
         | 
| 52 | 
            +
            - README.md
         | 
| 53 | 
            +
            - Rakefile
         | 
| 54 | 
            +
            - exe/zubat
         | 
| 55 | 
            +
            - lib/zubat.rb
         | 
| 56 | 
            +
            - lib/zubat/analizer.rb
         | 
| 57 | 
            +
            - lib/zubat/cli.rb
         | 
| 58 | 
            +
            - lib/zubat/commit.rb
         | 
| 59 | 
            +
            - lib/zubat/flog_wrapper.rb
         | 
| 60 | 
            +
            - lib/zubat/generator.rb
         | 
| 61 | 
            +
            - lib/zubat/git_command_wrapper.rb
         | 
| 62 | 
            +
            - lib/zubat/reek_wrapper.rb
         | 
| 63 | 
            +
            - lib/zubat/ruby_code.rb
         | 
| 64 | 
            +
            - lib/zubat/version.rb
         | 
| 65 | 
            +
            - screenshot.png
         | 
| 66 | 
            +
            - sig/zubat.rbs
         | 
| 67 | 
            +
            - templates/chart.html.erb
         | 
| 68 | 
            +
            homepage: https://github.com/mizoR/zubat
         | 
| 69 | 
            +
            licenses:
         | 
| 70 | 
            +
            - MIT
         | 
| 71 | 
            +
            metadata:
         | 
| 72 | 
            +
              homepage_uri: https://github.com/mizoR/zubat
         | 
| 73 | 
            +
              source_code_uri: https://github.com/mizoR/zubat
         | 
| 74 | 
            +
              changelog_uri: https://github.com/mizoR/zubat
         | 
| 75 | 
            +
            post_install_message: 
         | 
| 76 | 
            +
            rdoc_options: []
         | 
| 77 | 
            +
            require_paths:
         | 
| 78 | 
            +
            - lib
         | 
| 79 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 80 | 
            +
              requirements:
         | 
| 81 | 
            +
              - - ">="
         | 
| 82 | 
            +
                - !ruby/object:Gem::Version
         | 
| 83 | 
            +
                  version: 3.2.0
         | 
| 84 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 85 | 
            +
              requirements:
         | 
| 86 | 
            +
              - - ">="
         | 
| 87 | 
            +
                - !ruby/object:Gem::Version
         | 
| 88 | 
            +
                  version: '0'
         | 
| 89 | 
            +
            requirements: []
         | 
| 90 | 
            +
            rubygems_version: 3.4.10
         | 
| 91 | 
            +
            signing_key: 
         | 
| 92 | 
            +
            specification_version: 4
         | 
| 93 | 
            +
            summary: zubat
         | 
| 94 | 
            +
            test_files: []
         |