Revision 7614 (by gradha, 2006/12/02 18:13:42) Whitespace cleanup.
#!/usr/bin/env python
# -*- mode:Python; tab-width: 3 -*-
"""Gets some statistics about authors from a CVS repository.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

$Id: cvs-author-statistics.py 7614 2006-12-02 18:13:42Z gradha $
"""

__author__ = "Grzegorz Adam Hankiewicz"
__date__ = "$Date: 2006-12-02 11:13:42 -0700 (Sat, 02 Dec 2006) $"
__version__ = "$Revision: 7614 $"
__email__ = "gradha@users.sourceforge.net"
__credits__ = ""


import time
import popen2
import re
import string
import sys


HUMAN_VERSION = "0.1.2"
CVS_DATE_FORMAT = "%Y/%m/%d %H:%M:%S"
SECONDS_IN_A_DAY = 60 * 60 * 24
CVS_REGEX = re.compile(r"date: (?P<date>[^;]+);\s+author: (?P<name>[^;]+);")


def usage_information(exit_code = 0, binary_name = "cvs-author-statistics.py"):
   """Prints usage information and terminates execution."""
   print """Usage: %s [-hv]

-h, --help
   Print this help screen.
-v, --version
   Print version number and exit.
-i xxx, --ignore xxx
   Comma separated list of people ignored in the statistic.

Usage examples:
 %s -i anoncvs_pisg
""" % (binary_name, binary_name)
   sys.exit(exit_code)


def process_command_line(argv = None):
   """Extracts from argv the options and returns them in a tuple.

   This function is a command line wrapper against main_process,
   it returns a tuple which you can `apply' calling main_process. If
   something in the command line is missing, the program will exit
   with a hopefully helpfull message.

   args should be a list with the full command line. If it is None
   or empty, the arguments will be extracted from sys.argv. The
   correct format of the accepted command line is documented by
   usage_information.
   """
   import getopt
   if not argv:
      argv = sys.argv

   short_options = "hvi:f:"
   long_options = ["help", "version", "ignore=", "file="]

   try:
      opts, args = getopt.getopt(argv[1:], short_options, long_options)
   except getopt.error, msg:
      print "Error processing command line: %s\n" % msg
      usage_information(2)

   ignored_users = {}
   file_output = None

   for option, value in opts:
      if option in ("-h", "--help"):
         usage_information()
      elif option in ("-v", "--version"):
         print HUMAN_VERSION
         sys.exit(0)
      elif option in ("-i", "--ignore"):
         for user in string.split(value, ","):
            ignored_users[user] = 1
      elif option in ("-f", "--file"):
         file_output = value

   ignored_users = ignored_users.keys()
   return ignored_users, file_output


def get_time_information_from_cvs(ignored_users):
   """Retrieves author information from CVS as a dictionary."""

   child_stdout, child_stdin = popen2.popen2(["cvs", "log"])
   child_stdin.close()
   line = child_stdout.readline()
   authors = {}
   while line:
      match = CVS_REGEX.match(line)
      if match and match.group("name") not in ignored_users:
         date_in_seconds = time.mktime(
            time.strptime(match.group("date"), CVS_DATE_FORMAT))
         name = match.group("name")
         try:
            old_date = authors[name]
            if old_date < date_in_seconds:
               authors[name] = date_in_seconds
         except KeyError:
            authors[name] = date_in_seconds

      line = child_stdout.readline()
   child_stdout.close()
   return authors


def main_process(ignored_users, file_output):
   """Shows how long ago each user touched CVS.

   Pass in ignored users the list of usernames to ignore from the
   CVS log. file_output can be a string with the filename of where to
   put the results, otherwise data will be dumped on standard output.
   As an extra bonus, if the data to output is void, no file will be
   created.
   """
   authors = get_time_information_from_cvs(ignored_users)
   max_length = 0
   for name in authors.keys():
      max_length = max(max_length, len(name))

   current_time = time.time()
   names = authors.keys()
   names.sort()
   output_lines = []

   for name in names:
      difference = current_time - authors[name]
      if difference < 0:
         output_lines.append("%s touched CVS in a future timezone"
            "\n" % (string.ljust(name, max_length)))
      else:
         days = difference / SECONDS_IN_A_DAY
         months, days = divmod(days, 30)
         output_lines.append("%s touched CVS %02d months and %02d days "
            "ago\n" % (string.ljust(name, max_length), months, days))

   if output_lines:
      out = sys.stdout
      try:
         if file_output:
            out = open(file_output, "wt")
      except IOError, msg:
         print "Error opening %s\n%s" % (file_output, msg)

      out.writelines(output_lines)
      # Note we are lazy and don't close the output file.


if __name__ == "__main__":
   """Entry point of the script."""

   ignored_users, file_output = process_command_line()
   main_process(ignored_users, file_output)