#!/usr/bin/env ruby # Copyright (c) 2022 Yegor Bugayenko # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. STDOUT.sync = true require 'slop' require 'loog' require 'octokit' require 'nokogiri' require 'backtrace' require 'fileutils' require 'obk' require_relative '../lib/cobench/version' loog = Loog::REGULAR def config(path) f = File.expand_path(path) args = [] args += File.readlines(f).map(&:strip) if File.exist?(f) args end args = config('~/.cobench') + config('.cobench') + ARGV opts = Slop.parse(args, strict: true, help: true) do |o| o.banner = "Usage (#{Cobench::VERSION}): cobench [options]" o.bool '-h', '--help', 'Show these instructions' o.bool '--version', 'Show current version' o.bool '--verbose', 'Print as much log messages as possible' o.bool '--dry', 'Make no real round trips to GitHub' o.integer '--days', 'How many days to measure', default: 7 o.string '--to', 'Directory where to save all files to', default: './cobench' o.string '--token', 'GitHub authentication token' o.array '--coder', 'GitHub nickname of a coder to track' o.array '--metrics', 'Names of metrics to use (all by default)' o.array '--include', 'Mask of GitHub repo to include, e.g. yegor256/*' o.array '--exclude', 'Mask of GitHub repo to exclude' end if opts.help? || opts[:coder].empty? puts opts exit end if opts.verbose? loog = Loog::VERBOSE end if opts.version? loog.info(Cobench::VERSION) exit end Encoding.default_external = Encoding::UTF_8 Encoding.default_internal = Encoding::UTF_8 data = {} begin home = File.absolute_path(opts[:to]) loog.debug("All files generated will be saved to #{home}") if File.exist?(home) loog.debug("Directory #{home} exists") else FileUtils.mkdir_p(home) loog.debug("Directory #{home} created") end if opts.token? api = Octokit::Client.new(:access_token => opts[:token]) else api = Octokit::Client.new loog.warn("Connecting to GitHub without a token, this may lead to errors, use --token") end api.auto_paginate = true api = Obk.new(api, pause: 2000) loog.info("Reading GitHub data for the last #{opts[:days]} days") titles = {} opts[:coder].each do |u| user = u.downcase loog.info("Scanning #{user}...") data[user] = {} Dir[File.join(__dir__, '../lib/cobench/metrics/*.rb')].each do |f| name = File::basename(f).split('.')[0] if !opts[:metrics].empty? && !opts[:metrics].include?(name) loog.info("Ignoring #{user}/#{name} due to --metrics") next end type = "Cobench::#{name.capitalize}" loog.info("Reading #{user}/#{name}...") require_relative f m = type.split('::').reduce(Module, :const_get).new(api, user, opts) if opts.dry? measures = [ { title: 'Issues', total: Random.new.rand(100), href: 'https://github.com/' }, { title: 'Pulls', total: Random.new.rand(100), href: '' }, { title: 'HoC', total: Random.new.rand(100), href: '' }, { title: 'HoC', total: Random.new.rand(100), href: '' } ] else measures = m.take(loog) end measures.each do |d| before = 0 before += data[user][d[:title]][:total] if data[user][d[:title]] != nil data[user][d[:title]] = { total: d[:total] + before, href: d[:href] } titles[d[:title]] = d[:title] loog.info("The value of #{user}/#{d[:title]} is #{d[:total]}") end end end data.each do |u, ms| score = ms.map do |t, h| h[:total] * if t == 'HoC' 1 elsif t == 'Pulls' 100 elsif t == 'Issues' 50 elsif t == 'Commits' 5 else raise "Unknown title '#{t}'" end end.inject(0, :+) data[u]['Score'] = { total: score, href: '' } end builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| xml.cobench(time: Time.now, days: opts[:days]) do xml.titles do data.map { |_, ms| ms.keys }.flatten.each do |t| xml.title t end end xml.coders do data.each do |u, ms| xml.coder(id: u) do xml.metrics do ms.each do |k, v| xml.m(id: k, href: v[:href]) do xml.text v[:total] end end end end end end end end index = File.join(home, 'index.xml') xml = builder.to_xml loog.debug(xml) File.write(index, xml) loog.debug("XML saved to #{index} (#{File.size(index)} bytes)") xslt = Nokogiri::XSLT(File.read(File.join(__dir__, '../assets/index.xsl'))) html = xslt.transform(Nokogiri::XML(xml), 'version' => "'#{Cobench::VERSION}'") loog.debug(html) front = File.join(home, 'index.html') File.write(front, html) loog.debug("HTML saved to #{front} (#{File.size(front)} bytes)") rescue StandardError => e loog.error(Backtrace.new(e)) exit -1 end