| #!/usr/bin/env python  # coding: utf-8    """      This is the 'keepcool' module.        This module is a git repository inspection package      that aims to avoid developper's burnout.  """    from datetime import datetime, time  from optparse import OptionParser  from git import Git      class Date(object):      """          Class that handle date from a unix timestamp.            This class is used to determine if a date          belongs or not to the working hours            Please note that datetime is an immuatable object :          it is complicated to inherit from it...      """        def __init__(self, unix_timestamp):          """              It is complicated to inherit from datetime              wich is an immuatable object.          """          self.date = datetime.fromtimestamp(unix_timestamp)        def __repr__(self):          return self.date.strftime("Date(%s)")        def __str__(self):          ret = self.date.strftime("%A %Y-%m-%d %H:%M:%S")            extra = []          if not self.belongs_to_workin_hours():                if self.belongs_to_week_end():                  extra.append("week-end")                if self.belongs_to_early_morning():                  extra.append("early morning")                if self.belongs_to_late_evening():                  extra.append("late in the evening")                ret = ret + " -> " + str(extra)            return ret        def belongs_to_week_end(self):          """              Return True if date on saterday or sunday          """          return self.date.weekday() >= 5        def belongs_to_early_morning(self):          """              Return True if date is before 8:00 am          """          return self.date.time() < time(hour=8)        def belongs_to_late_evening(self):          """              Return True if date is after 20:00          """          return self.date.time() > time(hour=20)        def belongs_to_workin_hours(self):          """              Return TRUE if date belongs to workings hours.                Working hours include 8 am to 8 pm from monday to friday.          """          ret = True          if (self.belongs_to_week_end()                  or self.belongs_to_early_morning()                  or self.belongs_to_late_evening()):              ret = False          return ret      class Commit(Date):      """          This class is used to store GIT each commit object      """      def __init__(self, uuid, user, unix_timestamp):          Date.__init__(self, unix_timestamp)          self.uuid = uuid          self.user = user        def __repr__(self):          return "Commit('%s', %s, %s)" % (              self.uuid, repr(self.user), self.date.strftime("%s"))        def __str__(self):          return Date.__str__(self)      class User(object):      """          This class is used to store GIT committers      """      def __init__(self, name):          self.name = name          self.nb_in = 0          self.nb_out = 0          self.commits = {}        def __repr__(self):          return "User('%s')" % self.name        def __str__(self):          # still not very well understand          return (self.name).encode('utf-8')        def add_commit(self, commit):          """              Add a commit object if not already there.                Throw KeyError if already there.          """          if commit.uuid in self.commits:              raise IndexError(                  "'%s' commit's key is already recorded for '%s'" % (                      commit.uuid, self.name))          self.commits[commit.uuid] = commit        def compute_status(self):          """              Compute commits done by the user          """          self.nb_in = 0          self.nb_out = 0            for key in self.commits:              if self.commits[key].belongs_to_workin_hours():                  self.nb_in = self.nb_in + 1              else:                  self.nb_out = self.nb_out + 1        def print_status(self, trace=False):          """              Display commits done by the user          """            nb_sum = self.nb_in + self.nb_out          ratio = 100 * self.nb_out / nb_sum if nb_sum else 0          ret = ""            if trace is True:                def get_date(item):                  """sort commits by date"""                  return item[1].date                ret = ret + ".......................................\n"              items = sorted(self.commits.items(), key=get_date)                for asso in items:                  ret = ret + str(asso[1]) + "\n"            ret = ret + "%25s: %4i / %4i%%\n" % (str(self), nb_sum, ratio)          return ret      class KeepCool(object):      """ is the module's class.            Unique namespace to import :          >>> from keepcoll import KeepCool      """        def __init__(self, repo, **kwargs):          self.repo = repo          self.options = kwargs          self.users = {}          self.commits = {}          self.nb_in = 0          self.nb_out = 0        def __repr__(self):          return "KeepCool('%s')" % self.repo        def parse_logs(self, **kwargs):          """              Load logs into objects.          """          git = Git(self.repo)          log_args = ["--encoding=UTF-8", "--pretty=%H; %cN; %ct"]          only_user = None          after = None          before = None            # get context          if 'user' in kwargs:              only_user = kwargs['user']          elif 'user' in self.options:              only_user = self.options['user']            if 'after' in kwargs:              after = kwargs['after']          elif 'after' in self.options:              after = self.options['after']            if 'before' in kwargs:              before = kwargs['before']          elif 'before' in self.options:              before = self.options['before']            # add command line options          if only_user is not None:              log_args.append("--committer=" + only_user)          if after is not None:              log_args.append("--after=" + after)          if before is not None:              log_args.append("--before=" + before)            # query, parse and record git logs          string = git.log(*log_args)          for log in string.splitlines():              (uuid, name, unix_timestamp_string) = log.split(';')              self.add_commit(uuid, name, int(unix_timestamp_string))        def get_user(self, name):          """              Find and return an already recorded user.                Return None if not already there          """          ret = None          if name in self.users:              ret = self.users[name]          return ret        def add_user(self, name):          """              Add a new user object if not already there.                Return the user object.          """          ret = self.get_user(name)          if ret is None:              ret = self.users[name] = User(name)          return ret        def add_commit(self, uuid, name, unix_timestamp):          """              Add a new commit object if not already there.                Return the commit objet,              or throw KeyError if already there.          """          if uuid in self.commits:              raise IndexError("'%s' commit's key is already recorded" % uuid)            user = self.add_user(name)          ret = self.commits[uuid] = Commit(uuid, user, unix_timestamp)          user.add_commit(ret)          return ret        def compute_status(self):          """              Compute commits done by users          """          self.nb_in = 0          self.nb_out = 0            for key in self.users:              self.users[key].compute_status()              self.nb_in = self.nb_in + self.users[key].nb_in              self.nb_out = self.nb_out + self.users[key].nb_out        def print_status(self, **kwargs):          """              Display commits done by all user          """            def get_name(item):              """sort users by name"""              return item[1].name            def get_sum(item):              """sort users by sum"""              return item[1].nb_in + item[1].nb_out            def get_ratio(item):              """sort users by ratio"""              nb_sum = item[1].nb_in + item[1].nb_out              return 100 * item[1].nb_out / nb_sum if nb_sum else 0            trace = False          sort_field = 'name'          only_user = None            # get context          if 'trace' in kwargs:              trace = kwargs['trace']          elif 'trace' in self.options:              trace = self.options['trace']            if 'sort_field' in kwargs:              sort_field = kwargs['sort_field']          elif 'sort_field' in self.options:              sort_field = self.options['sort_field']            if 'user' in kwargs:              only_user = kwargs['user']          elif 'user' in self.options:              only_user = self.options['user']            nb_sum = self.nb_in + self.nb_out          ratio = 100 * self.nb_out / nb_sum if nb_sum else 0          ret = ""            ret = ret + "---------------------------------------\n"          ret = ret + "%25s: %4s / %4s\n" % (              self.repo + "'s commiter", "sum", "ratio")          ret = ret + "---------------------------------------\n"            # sort users          if sort_field == 'sum':              key_func = get_sum          elif sort_field == 'ratio':              key_func = get_ratio          else:              key_func = get_name            # loop on user          items = sorted(self.users.items(), key=key_func)          for asso in items:              ret = ret + asso[1].print_status(trace)            ret = ret + "---------------------------------------\n"            if only_user is None:              ret = ret + "%25s: %4i / %4i%%\n" % ("all", nb_sum, ratio)              ret = ret + "---------------------------------------\n"            return ret      def main():      """          Entry point if called as an executable            Note: optparse is deprecated, but fails to use argparse          within Debian Stretch using official packages.      """      parser = OptionParser(usage="usage: %prog [options] [git-path]",                            version="%prog 0.1")        # get options and parameters      parser.add_option(          "-v", "--verbose", action="store_true", dest="trace",          default="False", help="print commits too")      parser.add_option(          "-u", "--user", dest="user", default=None,          help="limit search to a USER", metavar="USER")      parser.add_option(          "-a", "--after", dest="after", default=None,          help="limit search from DATE", metavar="DATE")      parser.add_option(          "-b", "--before", dest="before", default=None,          help="limit search to DATE", metavar="DATE")      parser.add_option(          "-s", "--sort", dest="sort_field", default='name',          help="sort on FIELD: 'name' (default), 'sum', or 'ratio'",          metavar="FIELD")        (options, args) = parser.parse_args()      repo = args[0] if len(args) > 0 else "repo"        # call main API      keepcool = KeepCool(repo, user=options.user)      keepcool.parse_logs(after=options.after, before=options.before)      keepcool.compute_status()      print keepcool.print_status(sort_field=options.sort_field,                                  trace=options.trace)      if __name__ == "__main__":      main()  |