From f2924b3711dfafb373942ab1c190f140e1b143cd Mon Sep 17 00:00:00 2001 From: amygdala Date: Sun, 3 Oct 2010 16:25:04 -0500 Subject: [PATCH] Issue #475: Script to add GPL license to source file headers, can be used as a git pre-commit hook * Replaced use of os.rename to shutil.move to avoid this error on Windows/Cygwin: http://bytes.com/topic/python/answers/41652-errno-18-invalid-cross-device-link-using-os-rename * Added lic_header.py to .gitignore * Added usage instructions to README.md --- .gitignore | 3 +- extras/scripts/README.md | 32 +++- extras/scripts/header_content.txt | 16 ++ extras/scripts/lic_header.sample.py | 219 ++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 extras/scripts/header_content.txt create mode 100755 extras/scripts/lic_header.sample.py diff --git a/.gitignore b/.gitignore index 6b7b1750f2..5a9869a618 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ tests/config.tests.inc.php extras/cron/config extras/scripts/autodeploy-conf extras/scripts/migratedb-conf +extras/scripts/lic_header.py webapp/test_installer/* -build/* \ No newline at end of file +build/* diff --git a/extras/scripts/README.md b/extras/scripts/README.md index 7ae4c60f00..9de7899751 100644 --- a/extras/scripts/README.md +++ b/extras/scripts/README.md @@ -36,4 +36,34 @@ Iterates through all database migration files (including any new ones you're tes * Edit `migratedb-conf` to match your settings * Run `migratedb` script from thinkup root directory -Example: `./extras/scripts/migratedb` \ No newline at end of file +Example: `./extras/scripts/migratedb` + +## lic_header.py (pre-commit hook script for adding the license header to files) + +How to test: + +0. Install Python if necessary. (If you're using Windows, this script has been tested using Cygwin with Python and git installed.) + +1. Copy lic_header.sample.py to a new file lic_header.py, e.g. + cd extras/scripts; cp lic_header.sample.py lic_header.py + +2. Edit lic_header.py and change the THINKUP_HOME variable appropriately + +3. make lic_header.py executable, e.g. + chmod 755 lic_header.py + +4. Test the script by editing some existing php files, e.g. under webapps/_lib, to remove their headers. + You can run the script like this from the thinkup home dir: + % extras/scripts/lic_header.py + + (It has a few options; call it with --help to see them) + +5. To test the pre-commit hook part, create a file under the .git directory named .git/hooks/pre-commit, containing the following two lines: + +#!/bin/sh +./extras/scripts/lic_header.py + + Make this file executable (important). It will now run whenever you do a commit. If there were files updated by the script, the commit should not go through. + + Windows/Cygwin troubleshooting: If you get a "fatal error - unable to remap same address as parent", here's how to fix: + http://www.mylifestartingup.com/2009/04/fatal-error-unable-to-remap-to-same.html \ No newline at end of file diff --git a/extras/scripts/header_content.txt b/extras/scripts/header_content.txt new file mode 100644 index 0000000000..82ee21b291 --- /dev/null +++ b/extras/scripts/header_content.txt @@ -0,0 +1,16 @@ +* +* LICENSE: +* +* This file is part of ThinkUp (http://thinkupapp.com). +* +* ThinkUp 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. +* +* ThinkUp 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 ThinkUp. If not, see +* . +* diff --git a/extras/scripts/lic_header.sample.py b/extras/scripts/lic_header.sample.py new file mode 100755 index 0000000000..7e3540f05a --- /dev/null +++ b/extras/scripts/lic_header.sample.py @@ -0,0 +1,219 @@ +#! /usr/bin/env python + +# Copy this script to lic_header.py and edit the THINKUP_HOME variable appropriately. + +# This script updates the license/copyright information for all .php files (except those in the 'excludedir' list) +# which do not have license information added already. +# The script will indicate which files were modified, so that you can check them. +# This script is intended to be used either in standalone mode, or as a git pre-commit hook. +# ... +# #!/bin/sh +# ./extras/scripts/lic_header.py + + +import os, getopt, sys +import subprocess +import shutil + +#change this to the full path to your ThinkUp installation. E.g., +# "/home/username/yourpathto/ThinkUp" or +# "C:/yourpathto/ThinkUp" +THINKUP_HOME = None + +excludedir = ["/webapp/_lib/extlib", "/.git", "/webapp/_lib/view/compiled_view"] +ignorefns = ["config.sample.inc.php", "config.inc.php"] +license_line = "http://www.gnu.org/licenses/gpl.html" +header_fname = "/extras/scripts/header_content.txt" + +verbose = False +quiet = False +backup = False +suffix = ".php" +made_mods = False + +help_message = ''' +Usage: lic_header.py [options] + -b When modifying files, save "~" backup of original + -v Verbose output + -q Suppress all output + -l Specify license header content + --ltext Specify license header content + --help This help +''' + + +def update_source(filename, shortfn, copyright): + global backup + global made_mods + global ignorefns + + if (shortfn in ignorefns): + return + + utfstr = chr(0xef)+chr(0xbb)+chr(0xbf) + fdata = file(filename,"r+").read() + # arghh + has_cp = (fdata.find("LICENSE") > 0) and (fdata.find("This file is part of ThinkUp") > 0) + # print "has_cp: %s" % has_cp + if not has_cp: + made_mods = True + if not quiet: + print >> sys.stderr, "updating "+filename + isUTF = False + nl = get_copyright_namelist(filename, False) + filename_str = filename.replace(THINKUP_HOME, "ThinkUp") + copyright_str = "* " + filename_str + "\n*\n* Copyright (c) 2009-2010 " + nl +"\n" + + phpHeader = "" + if (fdata.startswith(utfstr)): + isUTF = True + fdata = fdata[3:] + if (fdata.startswith("> sys.stderr, "Excluding: %s" % fullfn + continue + if (os.path.isdir(fullfn)): + recursive_traversal(fullfn, copyright) + else: + if (fullfn.endswith(suffix)): + if verbose: + print "checking whether source needs updating:" + fullfn + update_source(fullfn, fn, copyright) + + +def build_docblock(nla, nlc): + if nla is not "": + nla += "\n" + docblock = ("%s" + build_docblock_lic_copy(nlc) +"*/\n") % (nla,) + return docblock + +def build_docblock_lic_copy(nl): + global license_line + return "* @license %s\n* @copyright 2009-2010 %s\n" % (license_line, nl) + +def get_copyright_namelist(filename, emailsp): + + #run a git command + if(emailsp): + sub = subprocess.Popen(['git', 'shortlog', '-se', '--numbered', 'HEAD', filename], + stdout=subprocess.PIPE) + else: + sub = subprocess.Popen(['git', 'shortlog', '-s', '--numbered', 'HEAD', filename], + stdout=subprocess.PIPE) + sub.wait() + oput = sub.stdout.readlines() + + # print "output: %s" % oput + clist= [numstrip(i, emailsp) for i in oput] + if len(clist) > 0: + if (emailsp): + return '\n'.join(clist) + else: + return ', '.join(clist) + else: + return "" + +def numstrip(line, emailsp): + nl = line.strip().split('\t')[1] + if (emailsp): + nl = nl.replace("@", "[at]") + nl = nl.replace(".", "[dot]") + nl = "* @author " + nl + return nl + + +class Usage(Exception): + def __init__(self, msg): + self.msg = msg + + +def main(argv=None): + + global verbose + global backup + global header_fname + global THINKUP_HOME + + if THINKUP_HOME is None: + print >> sys.stderr, "Edit this script to set THINKUP_HOME" + return 2 + + hfname = THINKUP_HOME + header_fname + + if argv is None: + argv = sys.argv + + try: + try: + opts, args = getopt.getopt(argv[1:], "l:vqb", ["help", "ltext="]) + except getopt.error, msg: + raise Usage(msg) + + # option processing + for option, value in opts: + # print "option %s, value %s" % (option, value) + if option == "-v": + verbose = True + print "setting verbose to true" + if option == "-q": + quiet = True + print "setting quiet to true" + if option == "-b": + backup = True + print "setting backup to true" + if option in ("-h", "--help"): + raise Usage(help_message) + if option in ("-l", "--ltext"): + hfname = value + + if verbose: + print "ThinkUp directory: %s" % (THINKUP_HOME,) + print "header file name: %s" % hfname + + cright = file(hfname,"r+").read() + recursive_traversal(THINKUP_HOME, cright) + if made_mods: + return 1 + else: + return 0 + + except Usage, err: + print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg) + print >> sys.stderr, "\t for help use --help" + return 2 + + +if __name__ == "__main__": + sys.exit(main()) +