class GithubContribs
Constants
- QUERY
- VERSION
- YEARS_QUERY
Public Instance Methods
generate(name, last, io = $stdout, testing = false)
click to toggle source
# File lib/github_contribs.rb, line 75 def generate name, last, io = $stdout, testing = false last ||= years(name).min last = last.to_i # string from cmdline unless testing then FileUtils.rm_f ".#{name}.#{Time.now.year}.json" # always fetch this fresh end steps = 16 # HACK: make it know the years automatically contribs = load_all name, last..Time.now.year d0, dN = Date.new(last), Date.today min, max = contribs.values.minmax max1 = Math.log(max+1) scale = ->(n) { [n, (steps * Math.log(n) / max1).floor] } total_contributions = contribs.values.sum range = (min..max) # used for legend below .group_by { |n| scale[n].last } .transform_values { |ary| m, n = ary.minmax m == n ? m : Range.new(m, n) } contribs.transform_values!(&scale) years = (d0.year..dN.year).to_a.reverse.to_h { |year| d0 = Date.new year d1 = Date.new year+1 d0 -= d0.wday # back it up to sunday to square everything off d1 += (7-d1.wday) # unless d1.wday == 0 days = (d0...d1).map { |d| d.year == year ? [d.to_s, contribs[d.to_s]] : [] } by_week = days.each_slice(7).to_a.transpose [ year, by_week ] } def io.td day, code, count if code && count then print '<td class="entry day green-color-%d tooltip">' % [code] print '<div class="right">%s = %s</div>' % [day, count] puts '</td>' else puts '<td class="entry day nocolor"></td>' end end io.puts "<html>" io.puts "<head>" io.puts "<title>%s's contribution calendar</title>" % [name] io.puts <<~CSS <style> body { background-color: #ffffff; font-family: system-ui; } .entry { font-size: 0.8rem; display: inline-block; margin: 1px; width: 12px; height: 12px; border-radius: 2px; outline: 1px solid rgba(0, 0, 0, 10%); outline-offset: -1px; } .entry.day:hover { outline: 2px solid rgba(0, 0, 0, 10%); } .non-day { outline: none; } /* cancel out entry's outline for non-days */ .tooltip { display: inline-block; position: relative; } .tooltip .right { font-family: monospace; font-size: 1.2rem; min-width: 11em; # "yyyy-mm-dd = xyz" = 16 chars? but em calculation is wack top: 50%; left: 100%; margin-left: 20px; transform: translate(0, -); padding: 5px 5px; color: #444; background-color: #ccc; border-radius: 8px; position: absolute; z-index: 99999999; box-sizing: border-box; border: 1px solid #fff; display: none; } .tooltip:hover .right { display: block; } .nocolor { background-color: hsl(120, 10%, 99%); } .green-color-0 { background-color: hsl(120 70% 95%); } .green-color-1 { background-color: hsl(120 70% 90%); } .green-color-2 { background-color: hsl(120 70% 85%); } .green-color-3 { background-color: hsl(120 70% 80%); } .green-color-4 { background-color: hsl(120 70% 75%); } .green-color-5 { background-color: hsl(120 70% 70%); } .green-color-6 { background-color: hsl(120 70% 65%); } .green-color-7 { background-color: hsl(120 70% 60%); } .green-color-8 { background-color: hsl(120 70% 55%); } .green-color-9 { background-color: hsl(120 70% 50%); } .green-color-10 { background-color: hsl(120 70% 45%); } .green-color-11 { background-color: hsl(120 70% 40%); } .green-color-12 { background-color: hsl(120 70% 35%); } .green-color-13 { background-color: hsl(120 70% 30%); } .green-color-14 { background-color: hsl(120 70% 25%); } .green-color-15 { background-color: hsl(120 70% 20%); } </style> CSS io.puts "</head>" io.puts "<body>" io.puts "<h1>#{name}'s complete github contributions</h1>" io.puts "<p><small>Total contributions = %d</small>" % [total_contributions] io.puts "<div><small>Legend: </small>" io.puts "<table>" io.puts "<tr>" io.puts '<td class="entry day nocolor"></td>' steps.times.each do |code| io.td "level #{code}", code, range[code] end io.puts "</tr>" io.puts "</table>" io.puts "</div>" years.each do |year, by_week| total = contribs .select { |date, (count, code)| date.start_with?(year.to_s) && count } .values .map(&:first) .sum io.puts "<h2>%d</h2>" % [year] io.puts '<table class="heatmap calendar">' by_week.each_with_index do |weekdays, idx| io.puts "<!-- #{year} #{Date::ABBR_DAYNAMES[idx]} -->" io.puts "<tr>" io.puts '<td class="entry non-day">%s</td>' % [[1, 3, 5].include?(idx) ? Date::ABBR_DAYNAMES[idx][0] : nil] weekdays.each_with_index do |(day, (count, code)), wday| if day then io.td day, code, count else io.puts '<td class="entry non-day"></td>' end end io.puts "</tr>" end io.puts "</table>" io.puts "<small>%d contributions</small>" % [total] end # years io.puts "</body>" io.puts "</html>" end
get(name, year)
click to toggle source
# File lib/github_contribs.rb, line 30 def get name, year path = ".#{name}.#{year}.json" unless File.exist? path then warn "#{name} #{year}" if $v data = graphql("--paginate", "--slurp", "-F", "userName=#{name}", "-F", "from=#{year}-01-01T00:00:00", "-f", "query=%s" % [QUERY]) .dig(0, "data", "user", # JFC "contributionsCollection", "contributionCalendar", "weeks") data = data.map { |h| h["contributionDays"] } data = data.map { |a| a.to_h { |h| [h["date"], h["contributionCount"]] } } data = data.reduce(&:merge).sort.to_h data = data.select { |k,v| k.start_with? "#{year}" } # edges of calendar data = data.select { |k,v| v > 0 } File.write path, JSON.pretty_generate(data) end JSON.parse File.read path end
graphql(*args)
click to toggle source
# File lib/github_contribs.rb, line 8 def graphql(*args) IO.popen(["gh", "api", "graphql", *args]) { |io| JSON.parse(io.read) } end
load_all(name, years)
click to toggle source
# File lib/github_contribs.rb, line 71 def load_all name, years years.map { |year| get name, year }.reduce(&:merge) end
years(name)
click to toggle source
# File lib/github_contribs.rb, line 66 def years name graphql("-F", "userName=#{name}", "-f", "query=%s" % [YEARS_QUERY]) .dig("data", "user", "contributionsCollection", "contributionYears") end