1#!/usr/bin/env python3 2# 3# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) 4# 5# SPDX-License-Identifier: BSD-2-Clause 6# 7 8import argparse 9import os 10import sys 11import re 12from datetime import timedelta, date 13from dateutil.parser import isoparse 14 15DESCRIPTION = 'Run sorry count statistics on repository' 16 17 18def cmd(c): 19 return os.popen(c).read() 20 21 22def log_lines(start, end, path): 23 """ 24 git log -p for the specified date period 25 """ 26 git_cmd = 'git log --date=local -p --since "%s" --before "%s" -- %s' 27 return cmd(git_cmd % (start, end, path)).splitlines() 28 29 30def sorries_current(path): 31 """ 32 git grep 33 """ 34 git_cmd = 'git grep sorry %s | wc -l' % path 35 return int(cmd(git_cmd).strip()) 36 37 38def repo_root(): 39 git_cmd = 'git rev-parse --show-toplevel' 40 return cmd(git_cmd)[:-1] 41 42 43def print_stats(deadline, delta, end, path): 44 start = end - delta 45 46 cur_path = os.getcwd() 47 os.chdir(repo_root()) 48 difflog = log_lines(start, end, path) 49 current = sorries_current(path) 50 os.chdir(cur_path) 51 52 sorry_added = re.compile("^\+.*sorry") 53 sorry_removed = re.compile("^-.*sorry") 54 lemma_added = re.compile("^\+.*lemma") 55 lemma_removed = re.compile("^-.*lemma") 56 author_line = re.compile("^Author:") 57 patch_line = re.compile("(^\+)|(^-)") 58 59 sorries_added = 0 60 sorries_removed = 0 61 lemmas_added = 0 62 lines = 0 63 64 authors = {} 65 66 for line in difflog: 67 if patch_line.match(line): 68 lines += 1 69 if sorry_added.match(line): 70 sorries_added += 1 71 if sorry_removed.match(line): 72 sorries_removed += 1 73 if lemma_added.match(line): 74 lemmas_added += 1 75 if lemma_removed.match(line): 76 lemmas_added -= 1 77 if author_line.match(line): 78 authors[line] = 1 79 80 balance = sorries_added-sorries_removed 81 82 removed_per_day = sorries_removed / delta.days 83 balance_per_day = balance / delta.days 84 85 if removed_per_day != 0: 86 projected_days = current / removed_per_day 87 else: 88 projected_days = -1 89 90 today = date.today() 91 projected_end = today + timedelta(days=projected_days) 92 93 days_left = deadline - today 94 needed_rate = current / days_left.days 95 96 days_diff = projected_days-days_left.days 97 if days_diff == 0: 98 over_under = 'precisely on target' 99 elif days_diff < 0: 100 over_under = '%d days early' % (-days_diff) 101 else: 102 over_under = '%d days late' % days_diff 103 104 print("Date range: {} to {} ({:.0f} weeks)".format(start, end, delta.days/7)) 105 print() 106 print("Patch lines: {:6d}".format(lines)) 107 print("Lemmas added: {:6d}".format(lemmas_added)) 108 print("Sorries added: {:6d}".format(sorries_added)) 109 print("Sorries removed: {:6d}".format(sorries_removed)) 110 print("Sorry balance: {:+6d}".format(balance)) 111 print("Active authors: {:6d}".format(len(list(authors)))) 112 print() 113 print("Sorries current: {:6d}".format(current)) 114 print() 115 print("Rate removed: {:6.1f} sorries per week".format(removed_per_day * 7)) 116 print("Rate balance: {:+6.1f} sorries per week".format(balance_per_day * 7)) 117 print("Rate needed: {:6.1f} sorries per week ({:+.1f} s/w)".format( 118 needed_rate * 7, (needed_rate-removed_per_day)*7)) 119 print() 120 print("Target end date: {0} (in {1} days)".format(deadline, days_left.days)) 121 if projected_days >= 0: 122 print("Projected date: {0} ({1})".format(projected_end, over_under)) 123 else: 124 print("Projected date: inf") 125 126 127if __name__ == '__main__': 128 # Setup the command line parser. 129 parser = argparse.ArgumentParser(description=DESCRIPTION) 130 parser.add_argument('-w', "--weeks", help="Number of weeks to look back", 131 type=int, default=4) 132 parser.add_argument('-e', "--end", help="End of the stats time period", 133 default=date.today().isoformat()) 134 parser.add_argument('-p', "--path", help="Restrict statistic to this path", 135 default=".") 136 parser.add_argument("deadline", help="Project deadline (yyyy-mm-dd)") 137 138 args = parser.parse_args() 139 140 delta = timedelta(weeks=args.weeks) 141 end = isoparse(args.end).date() 142 deadline = isoparse(args.deadline).date() 143 144 print_stats(deadline, delta, end, args.path) 145 sys.exit(0) 146