diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d9474a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +/testfiles diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..414cd13 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +:: + + Copyright (c) 2016 Johann Petrak + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..106f0c3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE.txt +include README.md + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..7b16757 --- /dev/null +++ b/README.rst @@ -0,0 +1,116 @@ +.. image:: https://img.shields.io/pypi/v/license-headers.svg + :target: https://pypi.python.org/pypi/license-headers/ + :alt: PyPi version + +.. image:: https://img.shields.io/pypi/pyversions/license-headers.svg + :target: https://pypi.python.org/pypi/license-headers/ + :alt: Python compatibility + +.. image:: https://img.shields.io/travis/elmotec/license-headers.svg + :target: https://travis-ci.org/elmotec/license-headers + :alt: Build Status + +.. image:: https://img.shields.io/pypi/dm/license-headers.svg + :alt: PyPi + :target: https://pypi.python.org/pypi/license-headers + +.. image:: https://coveralls.io/repos/elmotec/license-headers/badge.svg + :target: https://coveralls.io/r/elmotec/license-headers + :alt: Coverage + +.. image:: https://img.shields.io/codacy/474b0af6853a4c5f8f9214d3220571f9.svg + :target: https://www.codacy.com/app/elmotec/license-headers/dashboard + :alt: Codacy + + +======== +license-headers +======== + +A tool to update, change or add license headers to all files of any of +the supported types in or below some directory. + +Currently, the following file types are supported: Java/Scala/Groovy, bash/sh/csh, ... + + +Usage +----- + +:: + + usage: license-headers.py [-h] [-v] [-V] [-d directory] [-t template] [-y years] [-b] [-a] + [-c copyrightOwner] + + License Header Updater + + positional arguments: none + + optional arguments: + -h, --help show this help message and exit + -V, --version show program's version number and exit + -v, --verbose increases log verbosity (can be specified multiple times) + -d, --dir directory to process, all subdirectories will be included + -t, --tmpl template name or file to use (if not specified, -y must be specified) + -y, --years if template is specified, the year to substitute, otherwise this year + or year range will replace any existing year in existing headers. + Replaces variable ${years} in a template + -b, --backup for each file that gets changed, create a backup of the original with + the additional filename extension .bak + -c, --cr copyright owner, replaces variable ${owner} in a template + -a, --addonly add a header to all supported file types, ignore any existing headers. + + Examples: + # Add a new license header or replace any existing one based on the lgpl3 template. + # Process all files of supported type in or below the current directory. + # Use "Eager Hacker" as the copyright owner. + license-headers.py -t lgpl3 -c "Eager Hacker" + + +If license-headers is installed as a package (from pypi for instance), one can interact with it as a command line tool: + +:: + + python -m license-headers -t lgpl3 -c "Eager Hacker" + + +Installation +------------ + +Download ``license-headers.py`` from ``http://github.com/johann-petrak/license-headers`` or : + +:: + + pip install license-headers + + +Template names and files +------------------------ + +This library comes with a number of predefined templates. If a template name is specified +which when matched against all predefined template names matches exactly one as a substring, +then that template is used. Otherwise the name is expected to be the path of file. + +If a template does not contain any variables of the form `${varname}` it is used as is. +Otherwise the program will try to replace the variable from one of the following +sources: + +- an environment variable with the same name but the prefix `LICENSE_HEADERS_` added +- the command line option that can be used to set the variable (see usage) + + +Supported file types and how they are processed +----------------------------------------------- + +Java: +- assumed for all files with the extensions: .java, .scala, .groovy +- only headers that use Java block comments are recognised as existing headers +- the template text will be wrapped in block comments + +License +------- + +Licensed under the term of `MIT License`_. See attached file LICENSE.txt. + + +.. _MIT License: http://en.wikipedia.org/wiki/MIT_License + diff --git a/license-headers.py b/license-headers.py new file mode 100644 index 0000000..8b136db --- /dev/null +++ b/license-headers.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# encoding: utf-8 + +"""A tool to change or add license headers in all supported files in or below a directory.""" + +# Copyright (c) 2016 Johann Petrak +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from __future__ import unicode_literals +from __future__ import print_function + + +import os +import shutil +import sys +import logging +import argparse +import re +import fnmatch +from string import Template +import io +import subprocess + + +__version__ = '0.1' +__author__ = 'Johann Petrak' +__license__ = 'MIT' + + +log = logging.getLogger(__name__) + + +try: + unicode +except NameError: + unicode = str + + +## all filename pattherns of files we know how to process +patterns = ["*.java","*.scala","*.groovy","*.jape"] + +## how each file extension maps to a specific processing type +processingTypes = { + "java": "java", + "scala": "java", + "groovy": "java", + "jape": "java" + } + +## for each processing type, the detailed settings of how to process files of that type +typeSettings = { + "java": { + "headerStartPattern": "/*", ## used to find the beginning of a header bloc + "headerEndPattern": "*/", ## used to find the end of a header block + "headerStartLine": "/*\n", ## inserted before the first header text line + "headerEndLine": " */\n", ## inserted after the last header text line + "linePrefix": " * ", ## inserted before each header text line + "lineSuffix": "", ## inserted after each header text line, but before the new line + } +} + +def parse_command_line(argv): + """Parse command line argument. See -h option. + + Arguments: + argv: arguments on the command line must include caller file name. + + """ + import textwrap + + example = textwrap.dedent(""" + ## Some examples of how to use this command! + """).format(os.path.basename(argv[0])) + formatter_class = argparse.RawDescriptionHelpFormatter + parser = argparse.ArgumentParser(description="Python license header updater", + epilog=example, + formatter_class=formatter_class) + parser.add_argument("-V", "--version", action="version", + version="%(prog)s {}".format(__version__)) + parser.add_argument("-v", "--verbose", dest="verbose_count", + action="count", default=0, + help="increases log verbosity (can be specified " + "multiple times)") + parser.add_argument("-d", "--dir", dest="dir", nargs=1, + help="The directory to recursively process.") + parser.add_argument("-t", "--tmpl", dest="tmpl", nargs=1, + help="Template name or file to use.") + parser.add_argument("-y", "--years", dest="years", nargs=1, + help="Year or year range to use.") + parser.add_argument("-o", "--owner", dest="owner", nargs=1, + help="Name of copyright owner to use.") + parser.add_argument("-n", "--projname", dest="projectname", nargs=1, + help="Name of project to use.") + parser.add_argument("-u", "--projurl", dest="projecturl", nargs=1, + help="Url of project to use.") + arguments = parser.parse_args(argv[1:]) + + # Sets log level to WARN going more verbose for each new -V. + log.setLevel(max(3 - arguments.verbose_count, 0) * 10) + return arguments + + +def get_paths(patterns, start_dir="."): + """Retrieve files that match any of the glob patterns from the start_dir and below.""" + for root, dirs, files in os.walk(start_dir): + names = [] + for pattern in patterns: + names += fnmatch.filter(files, pattern) + for name in names: + path = os.path.join(root, name) + yield path + +## return an array of lines, with all the variables replaced +## throws an error if a variable cannot be replaced +def read_template(templateFile,dict): + with open(templateFile,'r') as f: + lines = f.readlines() + lines = [Template(line).substitute(dict) for line in lines] ## use safe_substitute if we do not want an error + return lines + +def main(): + """Main function.""" + logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + try: + error = False + settings = { + } + templateLines = None + arguments = parse_command_line(sys.argv) + if arguments.dir: + start_dir = arguments.dir[0] + else: + start_dir = "." + if arguments.years: + settings["years"] = arguments.years[0] + if arguments.owner: + settings["owner"] = arguments.owner[0] + if arguments.projectname: + settings["projectname"] = arguments.projectname[0] + if arguments.projecturl: + settings["projecturl"] = arguments.projecturl[0] + ## if we have a template name specified, try to get or load the template + if arguments.tmpl: + opt_tmpl = arguments.tmpl[0] + ## first get all the names of our own templates + ## for this get first the path of this file + templatesDir = os.path.join(os.path.dirname(os.path.abspath(__file__)),"templates") + ## get all the templates in the templates directory + templates = [f for f in get_paths("*.tmpl",templatesDir)] + templates = [(os.path.splitext(os.path.basename(t))[0],t) for t in templates] + ## filter by trying to match the name against what was specified + tmpls = [t for t in templates if opt_tmpl in t[0]] + if len(tmpls) == 1: + tmplName = tmpls[0][0] + tmplFile = tmpls[0][1] + print("Using template ",tmplName) + templateLines = read_template(tmplFile,settings) + else: + if len(tmpls) == 0: + ## check if we can interpret the option as file + if os.path.isfile(opt_tmpl): + print("Using file ",os.path.abspath(opt_tmpl)) + templateLines = read_template(os.path.abspath(opt_tmpl),settings) + else: + print("Not a built-in template and not a file, cannot proceed: ",opt_tmpl) + error = True + else: + ## notify that there are multiple matching templates + print("There are multiple matching template names: ",[t[0] for t in tmpls]) + error = True + else: # no tmpl parameter + if not arguments.years: + print("No template specified and no years either, nothing to do") + error = True + if not error: + logging.debug("Got template lines: ",templateLines) + ## now do the actual processing: if we did not get some error, we have a template loaded or no template at all + ## if we have no template, then we will have the years. + ## now process all the files and either replace the years or replace/add the header + print("Processing directory ",start_dir) + for file in get_paths(patterns,start_dir): + print("Processing file: ",file) + ## + finally: + logging.shutdown() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..bcca040 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5abe229 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# encoding: utf-8 + +"""Packaging script.""" + +import os +from setuptools import setup + +here = os.path.abspath(os.path.dirname(__file__)) +readme = open(os.path.join(here, 'README.rst')).read() + +setup( + name="license-headers", + version="0.1", + author="Johann Petrak", + author_email="johann.petrak@gmail.com", + description='Add or change license headers for all files in a directory', + license="MIT", + keywords="", + url="http://github.com/johann-petrak/license-headers", + py_modules=['license-headers'], + entry_points={'console_scripts': ['license-headers=license-headers:main']}, + long_description=readme, + # test_suite='tests', + setup_requires=[], + # tests_require=['mock'], + classifiers=["Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Environment :: Console", + "Natural Language :: English", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Topic :: Software Development", + "Topic :: Software Development :: Code Generators", + "Intended Audience :: Developers", + ], +) diff --git a/templates/agpl-v3.tmpl b/templates/agpl-v3.tmpl new file mode 100644 index 0000000..15b3f14 --- /dev/null +++ b/templates/agpl-v3.tmpl @@ -0,0 +1,17 @@ +Copyright (c) ${years} ${owner}. + +This file is part of ${projectname} +(see ${projecturl}). + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/templates/apache-2.tmpl b/templates/apache-2.tmpl new file mode 100644 index 0000000..631f6a0 --- /dev/null +++ b/templates/apache-2.tmpl @@ -0,0 +1,21 @@ +Copyright (c) ${years} ${owner}. + +This file is part of ${projectname} +(see ${projecturl}). + +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. diff --git a/templates/bsd-3.tmpl b/templates/bsd-3.tmpl new file mode 100644 index 0000000..2828051 --- /dev/null +++ b/templates/bsd-3.tmpl @@ -0,0 +1,6 @@ +Copyright (c) ${years} ${owner}. + +This file is part of ${projectname} +(see ${projecturl}). + +License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause diff --git a/templates/gpl-v3.tmpl b/templates/gpl-v3.tmpl new file mode 100644 index 0000000..de7d736 --- /dev/null +++ b/templates/gpl-v3.tmpl @@ -0,0 +1,17 @@ +Copyright (c) ${years} ${owner}. + +This file is part of ${projectname} +(see ${projecturl}). + +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 3 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, see . diff --git a/templates/lgpl-v2.1.tmpl b/templates/lgpl-v2.1.tmpl new file mode 100644 index 0000000..572655b --- /dev/null +++ b/templates/lgpl-v2.1.tmpl @@ -0,0 +1,17 @@ +Copyright (c) ${years} ${owner}. + +This file is part of ${projectname} +(see ${projecturl}). + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . diff --git a/templates/lgpl-v3.tmpl b/templates/lgpl-v3.tmpl new file mode 100644 index 0000000..357d105 --- /dev/null +++ b/templates/lgpl-v3.tmpl @@ -0,0 +1,17 @@ +Copyright (c) ${years} ${owner}. + +This file is part of ${projectname} +(see ${projecturl}). + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see .