为源代码文件添加许可证头文件的工具?

98

我正在寻找一种可以批量向一些源文件添加许可证头信息的工具,其中一些文件已经包含了头信息。是否有这样一种工具,可以在头信息不存在时插入头信息?

编辑:我故意没有标记这个问题的答案,因为答案基本上都是环境特定和主观的。


5
我有意不对这个问题做出答案标记,因为答案基本上都是特定于环境和主观的。您是否正在寻找一个环境无关的解决方案,例如伪代码?如果不是,请告诉我们您正在使用的环境。 - jrummell
1
jrummell:不,我不是在寻找一个与环境无关的解决方案。我是在寻找一个我们团队可以使用的多环境解决方案。 - Alex Lyman
@AlexLyman。不选择答案是一种卑鄙的行为。 - Mad Physicist
@MadPhysicist,这就是你为什么要对一个非常好的问题发起关闭投票的原因吗? - CrazyCasta
@CrazyCasta。关闭投票的原因在自动生成的简介中已经很明显了。大致上来说,就是先彻底地搜索谷歌,然后再提问。 - Mad Physicist
显示剩余4条评论
12个回答

71
#!/bin/bash

for i in *.cc # or whatever other pattern...
do
  if ! grep -q Copyright $i
  then
    cat copyright.txt $i >$i.new && mv $i.new $i
  fi
done

1
对于"$@",使用for i是一个不错的选择。如果您的版本控制系统需要,您也可以在检出方面进行创新。 - Jonathan Leffler
11
-1,你应该引用"$i"。 - Aleks-Daniel Jakimenko-A.
我猜这个不能递归地在子目录中工作 :-( - knocte
5
请用以下方式替换for循环:for i in $(find /folder -name '*.cc');,这样可以在子目录上运行脚本。 - Joyce
这里有一个一行代码的解决方案 https://github.com/apache/skywalking-eyes#fix-license-header。你甚至可以在 GitHub Actions 中使用它来检查拉取请求,通过添加缺失的许可证头部建议进行审查,并自动修复头部。 - kezhenxu94
请注意在copyright.txt文件末尾添加一个换行符,否则脚本将替换它运行的每个文件的第一行。(至少在我的机器上是这样) - Newbyte

19

以下是一段Bash脚本,假设你的许可证头部信息在文件license.txt中:

文件addlicense.sh:

#!/bin/bash  
for x in $*; do  
head -$LICENSELEN $x | diff license.txt - || ( ( cat license.txt; echo; cat $x) > /tmp/file;  
mv /tmp/file $x )  
done  

现在在您的源目录中运行此命令:

export LICENSELEN=`wc -l license.txt | cut -f1 -d ' '`  
find . -type f \(-name \*.cpp -o -name \*.h \) -print0 | xargs -0 ./addlicense.sh  

1
如果文件名中包含数字,sed表达式将无法正常工作。相反,请考虑使用 cut -f1 -d ' ' - schweerelos
1
@Rosenfield 在导出语句中缺少闭合单引号。 - Talespin_Kit
为什么在find命令中需要括号?它对我来说失败了。 - knocte

16

Python 2的解决方案,根据您自己的需要进行修改

特点:

  • 处理UTF头信息(对于大多数IDE非常重要)
  • 递归更新目标目录中通过给定掩码传递的所有文件(修改“.endswith”参数以获得您语言的文件掩码(.c,.java等)
  • 能够覆盖以前的版权文本(提供旧版权参数即可)
  • 可以选择省略在excludedir数组中给出的目录
# updates the copyright information for all .cs files
# usage: call recursive_traversal, with the following parameters
# parent directory, old copyright text content, new copyright text content

import os

excludedir = ["..\\Lib"]

def update_source(filename, oldcopyright, copyright):
    utfstr = chr(0xef)+chr(0xbb)+chr(0xbf)
    fdata = file(filename,"r+").read()
    isUTF = False
    if (fdata.startswith(utfstr)):
        isUTF = True
        fdata = fdata[3:]
    if (oldcopyright != None):
        if (fdata.startswith(oldcopyright)):
            fdata = fdata[len(oldcopyright):]
    if not (fdata.startswith(copyright)):
        print "updating "+filename
        fdata = copyright + fdata
        if (isUTF):
            file(filename,"w").write(utfstr+fdata)
        else:
            file(filename,"w").write(fdata)

def recursive_traversal(dir,  oldcopyright, copyright):
    global excludedir
    fns = os.listdir(dir)
    print "listing "+dir
    for fn in fns:
        fullfn = os.path.join(dir,fn)
        if (fullfn in excludedir):
            continue
        if (os.path.isdir(fullfn)):
            recursive_traversal(fullfn, oldcopyright, copyright)
        else:
            if (fullfn.endswith(".cs")):
                update_source(fullfn, oldcopyright, copyright)
    
     
oldcright = file("oldcr.txt","r+").read()
cright = file("copyrightText.txt","r+").read()
recursive_traversal("..", oldcright, cright)
exit()

16

请查看copyright-header RubyGem,它支持拥有php、c、h、cpp、hpp、hh、rb、css、js和html扩展名的文件。它还可以添加和移除头信息。

通过键入“sudo gem install copyright-header”来安装它。

之后,您可以执行类似以下内容的操作:

copyright-header --license GPL3 \
  --add-path lib/ \
  --copyright-holder 'Dude1 <dude1@host.com>' \
  --copyright-holder 'Dude2 <dude2@host.com>' \
  --copyright-software 'Super Duper' \
  --copyright-software-description "A program that makes life easier" \
  --copyright-year 2012 \
  --copyright-year 2012 \
  --word-wrap 80 --output-dir ./

它还支持使用--license-file参数自定义许可证文件。


这很棒,但它不会删除自定义现有的标头 :( - pgpb.padilla
3
如果您为头文件创建了一个模板,您可以删除现有的头文件。将模板作为参数传递给脚本并带上 --license-file 参数,然后使用 --remove-path 标志从所有文件中去除该准确的头文件。基本上,由于有许多不同类型的头文件,创建可靠地移除它们的算法是非常困难的。 - Erik Osterman
1
我们最近添加了一个 Dockerfile,因此安装繁琐的 Ruby 依赖不再是问题。 - Erik Osterman

13

编辑: 如果你使用的是eclipse,有一个插件

我根据Silver Dragon的回答编写了一个简单的Python脚本。 我需要一个更灵活的解决方案,所以我想出了这个。 它允许你向一个目录中的所有文件递归地添加一个头文件。 你可以选择添加一个正则表达式来匹配文件名,一个正则表达式来匹配目录名,以及一个正则表达式来匹配文件的第一行不应该匹配的内容。 你可以使用最后一个参数来检查头文件是否已经被包含。

如果文件的第一行以shebang(#!)开头,这个脚本将自动跳过该行。这样做是为了不破坏依赖于此的其他脚本。 如果你不希望有这种行为,你需要在writeheader函数中注释掉3行代码。

这就是它:

#!/usr/bin/python
"""
This script attempts to add a header to each file in the given directory 
The header will be put the line after a Shebang (#!) if present.
If a line starting with a regular expression 'skip' is present as first line or after the shebang it will ignore that file.
If filename is given only files matchign the filename regex will be considered for adding the license to,
by default this is '*'

usage: python addheader.py headerfile directory [filenameregex [dirregex [skip regex]]]

easy example: add header to all files in this directory:
python addheader.py licenseheader.txt . 

harder example adding someone as copyrightholder to all python files in a source directory,exept directories named 'includes' where he isn't added yet:
python addheader.py licenseheader.txt src/ ".*\.py" "^((?!includes).)*$" "#Copyright .* Jens Timmerman*" 
where licenseheader.txt contains '#Copyright 2012 Jens Timmerman'
"""
import os
import re
import sys

def writeheader(filename,header,skip=None):
    """
    write a header to filename, 
    skip files where first line after optional shebang matches the skip regex
    filename should be the name of the file to write to
    header should be a list of strings
    skip should be a regex
    """
    f = open(filename,"r")
    inpt =f.readlines()
    f.close()
    output = []

    #comment out the next 3 lines if you don't wish to preserve shebangs
    if len(inpt) > 0 and inpt[0].startswith("#!"): 
        output.append(inpt[0])
        inpt = inpt[1:]
        
    if skip and skip.match(inpt[0]): #skip matches, so skip this file
        return
    
    output.extend(header) #add the header
    for line in inpt:
        output.append(line)
    try:
        f = open(filename,'w')
        f.writelines(output)
        f.close()
        print "added header to %s" %filename
    except IOError,err:
        print "something went wrong trying to add header to %s: %s" % (filename,err)


def addheader(directory,header,skipreg,filenamereg,dirregex):
    """
    recursively adds a header to all files in a dir
    arguments: see module docstring
    """
    listing = os.listdir(directory)
    print "listing: %s " %listing
    #for each file/dir in this dir
    for i in listing:
        #get the full name, this way subsubdirs with the same name don't get ignored
        fullfn = os.path.join(directory,i) 
        if os.path.isdir(fullfn): #if dir, recursively go in
            if (dirregex.match(fullfn)):
                print "going into %s" % fullfn
                addheader(fullfn, header,skipreg,filenamereg,dirregex)
        else:
            if (filenamereg.match(fullfn)): #if file matches file regex, write the header
                writeheader(fullfn, header,skipreg)


def main(arguments=sys.argv):
    """
    main function: parses arguments and calls addheader
    """
    ##argument parsing
    if len(arguments) > 6 or len(arguments) < 3:
        sys.stderr.write("Usage: %s headerfile directory [filenameregex [dirregex [skip regex]]]\n" \
                         "Hint: '.*' is a catch all regex\nHint:'^((?!regexp).)*$' negates a regex\n"%sys.argv[0])
        sys.exit(1)
    
    skipreg = None
    fileregex = ".*"
    dirregex = ".*"
    if len(arguments) > 5:
        skipreg = re.compile(arguments[5])
    if len(arguments) > 3:
        fileregex =  arguments[3]
    if len(arguments) > 4:
        dirregex =  arguments[4]
    #compile regex    
    fileregex = re.compile(fileregex)
    dirregex = re.compile(dirregex)
    #read in the headerfile just once
    headerfile = open(arguments[1])
    header = headerfile.readlines()
    headerfile.close()
    addheader(arguments[2],header,skipreg,fileregex,dirregex)

#call the main method
main()

3
插件的链接失效了。 - mjaggard
我认为这可能就是它:https://wiki.eclipse.org/Development_Resources/How_to_Use_Eclipse_Copyright_Tool - mbdevpl
在编写自己的Python包版本之前,我没有彻底地搜索谷歌。我可能会借鉴您的解决方案进行未来的改进。https://github.com/zkurtz/license_proliferator - zkurtz
1
@mjaggard 需要修复的链接应该被修复。 - Jens Timmerman

12

对于Gradle,插件是:https://github.com/hierynomus/license-gradle-plugin - koppor

11

这是一个简单的仅适用于Windows的UI工具,它可以在文件夹中搜索指定类型的所有文件,将您想要添加的文本(即许可证文本)添加到每个文件的开头,并将结果复制到另一个目录中(避免潜在的覆盖问题)。该工具是免费的,需要 .Net 4.0。

实际上,我是这个工具的作者,如果您有任何问题或新功能请求,请随时与我联系…但不能保证交付时间表。 ;)

更多信息:许可证头工具Amazify.com


此外,我会感激任何对此的反馈,谢谢。 - Brady Moritz
1
我非常喜欢这个软件,但是需要一个宏来将文件名输入到页眉中。同时,显示要编辑的文件列表并提供排除文件的选项也会很不错。(: - hs2d
谢谢,宏和排除列表是一个好主意。 - Brady Moritz
您的链接已过期。无法从网站下载。 - valijon
谢谢,我会把它修好的。 - Brady Moritz

5

请查看license-adder。它支持多个代码文件(甚至自定义的文件),并且正确处理现有的头文件。已经配备了最常见的开源许可证模板。


1
谢谢,您具体是指哪个 license-adder 工具?我找到了 license-adder - free .NET application - Google Project HostingLicense-Adder · simple python script · GitHub - sdaau
GitHub现在可以找到:https://github.com/sanandrea/License-Adder - koppor

4

Here is one I rolled in PHP to modify PHP files. I also had old license information to delete so it replaces the old text first, then adds the new text immediately after the opening

<?php
class Licenses
{
    protected $paths = array();
    protected $oldTxt = '/**
 * Old license to delete
 */';
    protected $newTxt = '/**
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */';

    function licensesForDir($path)
    {
        foreach(glob($path.'/*') as $eachPath)
        {
            if(is_dir($eachPath))
            {
                $this->licensesForDir($eachPath);
            }
            if(preg_match('#\.php#',$eachPath))
            {
                $this->paths[] = $eachPath;
            }
        }
    }

    function exec()
    {

        $this->licensesForDir('.');
        foreach($this->paths as $path)
        {
            $this->handleFile($path);
        }
    }

    function handleFile($path)
    {
        $source = file_get_contents($path);
        $source = str_replace($this->oldTxt, '', $source);
        $source = preg_replace('#\<\?php#',"<?php\n".$this->newTxt,$source,1);
        file_put_contents($path,$source);
        echo $path."\n";
    }
}

$licenses = new Licenses;
$licenses->exec();


3

这里有一个我在Apache列表中找到的。它是用Ruby编写的,看起来很容易阅读。你甚至可以从rake中调用它,让它变得更加特别好用。 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接