cato

Module cato |
[hide private]
[frames] | no frames]

# Source Code for Module cato

      1  # coding=utf-8
      2
      3  #
      4  #
      5  #    Copyright (C) 2012  Marco Bartolini, marco.bartolini@gmail.com
      6  #
      7  #    This program is free software: you can redistribute it and/or modify
      8  #    it under the terms of the GNU General Public License as published by
      9  #    the Free Software Foundation, either version 3 of the License, or
     10  #    (at your option) any later version.
     11  #
     12  #    This program is distributed in the hope that it will be useful,
     13  #    but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15  #    GNU General Public License for more details.
     16  #
     17  #    You should have received a copy of the GNU General Public License
     18  #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     19  #
     20  """
     21  This module implements the necessary routines used to embed license
     22  informations into source code project files.
     23  The package installation also installs a command line utility along with the
     24  license files in textual format in a hidden directory inside the user home.
     25
     26  @author: Marco Bartolini
     27  @contact: marco.bartolini@gmail.com
     28  @version: 1.0
     29  """
     30
     31  import os
     32  import getpass
     33  import datetime
     34  import re
     35
     36 -class Cato(object):
     37 -    def __init__(self):
     38          """
     39          Default constructor sets default values for cato options.
     40          """
     41          self.home_path = os.path.expanduser("~")
     42          self.cato_dir = os.path.join(self.home_path, ".cato")
     43          self.license_dir = os.path.join(self.cato_dir, "licenses")
     44          self.end_phrase = "END OF TERMS AND CONDITIONS"
     45
     46          self.comment_syntax = {"c" : "//",
     47                  "cpp" : "//",
     48                  "cc" : "//",
     49                  "h" : "//",
     50                  "hpp" : "//",
     51                  "py" : "#",
     52                  "java" : "//",
     53                  "f" : "!",
     54                  "rb" : "#",
     55                  "default" : "*",
     56                  }
     57
     58          self.license_tags = {"<year>" : str(datetime.datetime.now().year),
     59                  "<owner>" : getpass.getuser(),
     60                  "<email>" : getpass.getuser() + "@example.com",
     61                  }
     62
     63          self.eol = "\n"
     64          self.loaded = False
     65
     66 -    def parse_license(self, lic_name):
     67          """
     68          Parse a license file and split it into the full flagged license to
     69          be included into a \"LICENSE\" file and an embedded version to be
     70          included at the beginning of each source file. It also substitute the
     71          license tags in the license text.
     72          @type lic_name: string
     73          @param lic_name: license file name key
     74          @return: (full_licensem, embedded_license)
     75          """
     76          licenses = {}
     77          for license_file in os.listdir(self.license_dir):
     78              licenses[license_file[:license_file.rfind(".")]] = \
     79                  os.path.abspath(os.path.join(self.license_dir, license_file))
     80          try:
     81              with open(licenses[lic_name], "rt") as f:
     82                  text = f.read()
     83          except KeyError:
     84              raise KeyError("License " + lic_name + " not found in " +
     85                      self.license_dir)
     86          for tag, substitution in self.license_tags.iteritems():
     87              pattern = re.compile(tag)
     88              text = re.subn(pattern, substitution, text)[0]
     89          license_parts = text.split(self.end_phrase)
     90          extended_license = license_parts[0]
     91          if len(license_parts) == 1:
     92              embedded_license = license_parts[0].split(self.eol)
     93          else:
     94              embedded_license = license_parts[1].split(self.eol)
     95          return (extended_license, embedded_license)
     96
     97 -    def patch_file(self, filename, embedded_license):
     98          """
     99          Embed the license in the given source code file. The license is inserted
    100          where the first empty line is found. If no empty line is present no license
    101          is embedded.
    102          @param filename: file path
    103          @param embedded_license: the embedded license as obtained by
    104          L{parse_license} function
    105          @return: True if a license has been embedded
    106          """
    107          extension = filename.split(".")[-1]
    108          comment = self.comment_syntax.get(extension, self.comment_syntax["default"])
    109          embedded = False
    110          with open(filename, "rt") as input:
    111              with open(filename + ".cato", "wt") as output:
    112                  for input_line in input:
    113                      output.write(input_line)
    114                      if not embedded and input_line.strip() == "":
    115                          for embed_line in embedded_license:
    116                              output.write(comment + embed_line + self.eol)
    117                          embedded = True
    118          os.rename(filename + ".cato", filename)
    119          return embedded
    120
    121 -    def patch_dir(self, dirname, extended_license):
    122          """
    123          Insert a license file into a directory naming it \"LICENSE\".
    124          @param dirname: the target directory path
    125          @param extended_license: the license text as obtained by L{parse_license}
    126          function
    127          """
    128          with open(os.path.join(dirname, "LICENSE"), "wt") as license_file:
    129              license_file.write(extended_license)
    130
    131 -def command_line_util(args):
    132      """
    133      Function that parses command line options and invokes cato methods on
    134      selected files. Use --help for online help.
    135      """
    136      from optparse import OptionParser #for compatibility with python2.5
    137      from ConfigParser import SafeConfigParser
    138      import os
    139      import fnmatch
    140
    141      cato_licenser = Cato()
    142
    143      # Parsing configuration file options
    144      scp = SafeConfigParser()
    145      scp.read(os.path.join(cato_licenser.cato_dir, "cato.cfg"))
    146      if 'Cato' in scp.sections():
    147          if 'owner' in scp.options('Cato'):
    148              cato_licenser.license_tags['<owner>'] = scp.get('Cato', 'owner')
    149          if 'email' in scp.options('Cato'):
    150              cato_licenser.license_tags['<email>'] = scp.get('Cato', 'email')
    151          if 'end_phrase' in scp.options('Cato'):
    152              cato_licenser.end_phrase = scp.get('Cato', 'end_phrase')
    153      if 'Comments' in scp.sections():
    154          for c in scp.options('Comments'):
    155              cato_licenser.comment_syntax[c] = scp.get('Comments', c)
    156
    157      # Parsing command line options
    158      op = OptionParser()
    159      op.add_option("--list", action="store_true", default=False,
    160              dest="lic_list", help="Lists available licenses and quit")
    161      op.add_option("-l", "--license", dest="license", help="The license name, use --list to list all licenses available")
    162      op.add_option("-o", "--owner", dest="owner", help="the copyright owner")
    163      op.add_option("-e", "--email", dest="email", help="email contact of the copyright owner")
    164      op.add_option("-y", "--year", dest="year", help="the copyright year, defaults to current year")
    165      op.add_option("-d", "--directory", dest="directory", help="a target directory where to find sources and add a LICENSE file")
    166      op.add_option("-r", action="store_true", default="False", dest="recursive",
    167              help="only with -d. If set recursively parses directory tree")
    168      op.add_option("-c", "--comment", dest="comment", help="overrides comment syntax")
    169      op.set_usage("cato -l gpl-3.0 -o \"John Doe\" -e john@doe.com -y 2012 *.py")
    170      op.set_description('''
    171  cato is free software for applying licenses to your source code
    172  files. You can customize cato changing the cato.cfg file that you
    173  find in ~/.cato/ and adding license textual files in
    174  ~/cato/licenses/.\n
    175  License files can contain <owner> and <email>
    176  textual tags which will be replaced with cato informations.\n
    177  Embedded license versions will be applied on the first empty line of
    178  each source file scanned, if no empty line is found, no license is
    179  applied.\n
    180  In its normal behaviour cato apply the license to the files given as
    181  arguments on the command line, while using -d option it scans the
    182  given directory for file extensions specified as command line
    183  arguments. If -r is provided in conjunction with -d, all the
    184  directory tree is scanned starting from the supplied dir.
    185              ''')
    186      options, args = op.parse_args(args)
    187
    188      # scan license directory and populate licenses dictionary
    189      licenses = []
    190      for lic_file in os.listdir(cato_licenser.license_dir):
    191          licenses.append(lic_file[:lic_file.rfind('.')])
    192
    193      # list available licenses
    194      if options.lic_list:
    195          for l in licenses:
    196              print l
    197          return
    198
    199      # overrides config file options
    200      if options.owner:
    201          cato_licenser.license_tags['<owner>'] = options.owner
    202      if options.year:
    203          cato_licenser.license_tags['<year>'] = options.year
    204      if options.email:
    205          cato_licenser.license_tags['<email>'] = options.email
    206      if options.comment:
    207          cato_licenser.comment_syntax = {'default': options.comment}
    208
    209      #extracts license infomrations
    210      if not options.license:
    211          license = licenses[0] #default license
    212      else:
    213          license = options.license
    214      (extended_lic, embedded_lic) = cato_licenser.parse_license(license)
    215
    216      # Apply the license to given sources
    217      if options.directory:
    218          dir = options.directory
    219          cato_licenser.patch_dir(dir, extended_lic)
    220          if options.recursive:
    221              for root, dirs, files in os.walk(dir):
    222                  for f in files:
    223                      match = False
    224                      for a in args:
    225                          if fnmatch.fnmatch(f, "*." + a):
    226                              match = True
    227                      if match:
    228                          print "Applying license to file: " + f
    229                          cato_licenser.patch_file(os.path.join(root, f), embedded_lic)
    230          else:
    231              root = os.path.abspath(dir)
    232              for f in os.listdir(root):
    233                  if os.path.isfile(os.path.join(root, f)):
    234                      match = False
    235                      for a in args:
    236                          if fnmatch.fnmatch(f, "*." + a):
    237                              match = True
    238                      if match:
    239                          print "Applying license to file: " + f
    240                          cato_licenser.patch_file(os.path.join(root, f), embedded_lic)
    241      else:
    242          for f in args:
    243              print "Applying license to file: " + f
    244              cato_licenser.patch_file(f, embedded_lic)
    245