view licenser/licenses.py @ 21:80193ae99aaf

fill out asterisk-style licenses
author Jeff Hammel <jhammel@mozilla.com>
date Thu, 24 Nov 2011 15:10:25 -0800
parents 272e10163900
children 9db63b0119de
line wrap: on
line source

# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# Mozilla.org.
# Portions created by the Initial Developer are Copyright (C) 2010
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#     Jeff Hammel <jhammel@mozilla.com>     (Original author)
#
# Alternatively, the contents of this file may be used under the terms of
# either of the GNU General Public License Version 2 or later (the "GPL"),
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

import os
import urllib2
from datetime import datetime
from string import Template

# types of comments

class CommentedFile(object):

    extensions = set()

    @classmethod
    def match(cls, filename):
        basename, extension = os.path.splitext(os.path.basename(filename))
        extension = extension.lstrip('.')
        if extension in cls.extensions:
            return cls(filename)

    def __init__(self, filename):
        self.filename = filename

    def isempty(self):
        return bool(file(self.filename).read().strip)

    def lines(self):
        if hasattr(self, '_lines'):
            return self._lines
        self._lines = file(self.filename).readlines()
        return self._lines

class AsteriskCommentsFile(CommentedFile):

    extensions = set(['c'])

    def __call__(self, license):

        if self.isempty():
            return # you're done
        lines = self.lines()
        
        # open the file for writing
        f = file(self.filename, 'w')

        # print the license
        license_lines = license.splitlines():
        for index, line in license_lines:
            prefix = ' *'
            suffix = ''
            if index == len(license_lines) - 1:
                suffix = ' */'
            if not index:
                prefix = '/*'
            print >> f, '%s %s%s' % (prefix, line, suffix)

        # print the rest of the file
        for line in lines:
            f.write(line)
        f.close()


class HashCommentsFile(CommentedFile):

    extensions = set(['py', 'sh'])

    def __call__(self, license):
        """interpolate the file"""

        if self.isempty():
            return # you're done
        lines = self.lines()

        # open the file for writing
        f = file(self.filename, 'w')

        # print shebang if it exists
        if lines[0].startswith('#!'):
            shebang = lines.pop(0).strip()
            print >> f, shebang
            print >> f

        # print the license
        for line in license.splitlines():
            print >> f, '# %s' % line

        # print the rest of the file
        for line in lines:
            f.write(line)
        f.close()

    def isempty(self, path):
        """
        determines if a file is empty;  that is, contains only comments
        """
        for line in self.lines():
            line = line.strip()
            if line and line[0] != '#':
                return False
        return True


filetypes = [HashCommentsFile, AsteriskCommentsFile]

class License(object):
    """Abstract base class for a license"""

    variables = [] # required variables

    def __init__(self, filetypes=filetypes[:]):
        if self.template:
            if not os.path.isabs(self.template):
                self.template = os.path.join(os.path.dirname(__file__),
                                             'licenses',
                                             self.template)
            assert os.path.exists(self.template)
        self.filetypes = filetypes
        
    def license(self):
        return file(self.template).read()

    def print_license(self):
        print self.license()

    def token(self):
        """a token indicative of the license, by default the first line"""
        return self.license().splitlines()[0].strip()

    def has_license(self, filename):
        """does the file already have a license?"""
        token = self.token()
        for line in file(filename).readlines():
            if token in line:
                return True
        return False

    def __call__(self, directory, **kw):
        variables = self.obtain_variables(**kw)
        self.interpolate(directory, variables)

    def obtain_variables(self, **kw):
        for var in self.variables:
            if var not in kw:
                print 'Enter %s: ' % var,
                kw[var] = raw_input()
        self.pre(kw)
        return kw

    def pre(self, variables):
        """do anything that needs to be done with the variables before interpolation"""

    def filetype(self, _file):
        """determine the filetype for a given file"""
        for filetype in self.filetypes:
            match = filetype.match(_file)
            if match:
                return match

    def interpolate_file(self, _file, variables):

        # if the file is licensed, no need to proceed
        if self.has_license(_file):
            return # you're done

        # get the filetype
        filetype = self.filetype(_file)
        if not filetype:
            return # you're done

        # get the license
        # XXX should be cached
        license = self.license()
        license = Template(license).substitute(**variables)

        # add the license to the file
        filetype(license)

    def interpolate(self, directory, variables):
        for _file in self.files(directory):
            self.interpolate_file(_file, variables)

    def files(self, directory):
        files = []
        for dirpath, _, filenames in os.walk(directory):
            for f in filenames:
                files.append(os.path.join(dirpath, f))
        return files


class MPL(License):
    """Mozilla Public License"""
    template = 'MPL' # could be implicit here
    variables = [ 'author', 'email' ]
    url = 'http://www.mozilla.org/MPL/boilerplate-1.1/mpl-tri-license-txt'

    def license(self):

        if hasattr(self, '_license'):
            # return the cached copy
            return self._license
        
        # get the raw license
        raw = urllib2.urlopen(self.url).read()

        # now make it something not crazy
        lines = raw.splitlines()
        contributor_index = None
        for index, line in enumerate(lines):
            if line.startswith('The Original Code is'):
                lines[index] = 'The Original Code is mozilla.org code.'
            if line.startswith('_'):
                lines[index] = 'the Mozilla Foundation.'
            if '2___' in line:
                lines[index] = line.replace('2___', '${year}')
            if line.startswith('Contributor'):
                contributor_index = index

        assert contributor_index
        lines.insert(contributor_index+1, '  ${author} <${email}>')
        self._license = '\n'.join(lines)
        return self._license

    def pre(self, variables):
        variables['year'] = datetime.now().year