copyright-header 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/AUTHORS +2 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +69 -0
- data/Rakefile +1 -0
- data/bin/copyright-header +9 -0
- data/contrib/syntax.yml +61 -0
- data/copyright-header.gemspec +21 -0
- data/lib/copyright_header.rb +7 -0
- data/lib/copyright_header/command_line.rb +112 -0
- data/lib/copyright_header/parser.rb +227 -0
- data/lib/copyright_header/version.rb +3 -0
- data/licenses/BSD-2-CLAUSE.erb +9 -0
- data/licenses/BSD-3-CLAUSE.erb +10 -0
- data/licenses/BSD-4-CLAUSE.erb +11 -0
- data/licenses/GPL3.erb +17 -0
- data/licenses/MIT.erb +7 -0
- metadata +83 -0
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
CopyrightHeader
|
2
|
+
===============
|
3
|
+
|
4
|
+
|
5
|
+
Features
|
6
|
+
------------
|
7
|
+
|
8
|
+
* Add/remove a copyright headers recursively on source files
|
9
|
+
* Customize the syntax configuration for how to write out comments
|
10
|
+
* Built-in support for GPL3 and MIT licenses
|
11
|
+
* Supports custom licenes with --license-file argument
|
12
|
+
* ERB template support
|
13
|
+
|
14
|
+
Caveats
|
15
|
+
-------
|
16
|
+
* Will only remove headers to files that have exactly the same header as the one we added
|
17
|
+
* Will only add headers to files which do not contain the pattern /[Cc]opyright|[Lc]icense/ in the first N lines
|
18
|
+
|
19
|
+
|
20
|
+
Requirements
|
21
|
+
------------
|
22
|
+
|
23
|
+
* Ruby 1.9.2 (supported version)
|
24
|
+
|
25
|
+
Installation & Usage
|
26
|
+
--------------------
|
27
|
+
|
28
|
+
# gem install copyright-header
|
29
|
+
# copyright-header --help
|
30
|
+
|
31
|
+
Add a GPL3 License header to a file:
|
32
|
+
|
33
|
+
copyright-header --add-path /tmp/test.rb --license GPL3 --dry-run
|
34
|
+
|
35
|
+
Remove the header created in the previous step (without --dry-run argument):
|
36
|
+
|
37
|
+
copyright-header --remove-path /tmp/test.rb --license GPL3 --dry-run
|
38
|
+
|
39
|
+
Paths can be either files or directories. It will recursively traverse the directory tree ignoring all dot files.
|
40
|
+
|
41
|
+
You can specify an alternative syntax configuration file using the --syntax argument.
|
42
|
+
|
43
|
+
|
44
|
+
Full list of supported arguments:
|
45
|
+
|
46
|
+
Usage: copyright-header options [file]
|
47
|
+
-n, --dry-run Output the parsed files to STDOUT
|
48
|
+
-o, --output-dir DIR Use DIR as output directory
|
49
|
+
--license-file FILE Use FILE as header
|
50
|
+
--license [GPL3|MIT] Use LICENSE as header
|
51
|
+
--copyright-software NAME The common name for this piece of software (e.g. "Copyright Header")
|
52
|
+
--copyright-software-description DESC
|
53
|
+
The common name for this piece of software (e.g. "A utility to manipulate copyright headers on source code files")
|
54
|
+
--copyright-holder NAME The common name for this piece of software (e.g. "Erik Osterman <e@osterman.com>"). Repeat argument for multiple names.
|
55
|
+
--copyright-year YEAR The common name for this piece of software (e.g. "2012"). Repeat argument for multiple years.
|
56
|
+
-w, --word-wrap LEN Maximum number of characters per line for license (default: 80)
|
57
|
+
-a, --add-path PATH Recursively insert header in all files found in path
|
58
|
+
-r, --remove-path PATH Recursively remove header in all files found in path
|
59
|
+
-c, --syntax FILE Syntax configuration file
|
60
|
+
-V, --version Display version information
|
61
|
+
-h, --help Display this screen
|
62
|
+
|
63
|
+
|
64
|
+
Contact Information
|
65
|
+
-------------------
|
66
|
+
|
67
|
+
Erik Osterman <e@osterman.com>
|
68
|
+
http://www.osterman.com/
|
69
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.expand_path('.')) # Ruby 1.9 doesn't have . in the load path...
|
3
|
+
$:.push(File.expand_path('lib/'))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'copyright_header'
|
7
|
+
|
8
|
+
command_line = CopyrightHeader::CommandLine.new
|
9
|
+
command_line.execute
|
data/contrib/syntax.yml
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
ruby:
|
2
|
+
ext: ['.rb']
|
3
|
+
after: ['^#!']
|
4
|
+
comment:
|
5
|
+
open: '#\n'
|
6
|
+
close: '#\n'
|
7
|
+
prefix: '# '
|
8
|
+
|
9
|
+
html:
|
10
|
+
ext: ['.html', '.htm', '.xhtml', '.xml']
|
11
|
+
comment:
|
12
|
+
open: '<!--\n'
|
13
|
+
close: '-->\n'
|
14
|
+
prefix: ' '
|
15
|
+
|
16
|
+
php:
|
17
|
+
ext: ['.php']
|
18
|
+
after: [ '^#!' ]
|
19
|
+
comment:
|
20
|
+
open: '<?php \n/*\n'
|
21
|
+
close: ' */ ?>\n'
|
22
|
+
prefix: ' * '
|
23
|
+
|
24
|
+
|
25
|
+
javacript:
|
26
|
+
ext: ['.js']
|
27
|
+
comment:
|
28
|
+
open: '/*\n'
|
29
|
+
close: ' */\n\n'
|
30
|
+
prefix: ' * '
|
31
|
+
|
32
|
+
css:
|
33
|
+
ext: ['.css']
|
34
|
+
comment:
|
35
|
+
open: '/*\n'
|
36
|
+
close: ' */\n\n'
|
37
|
+
prefix: ' * '
|
38
|
+
|
39
|
+
|
40
|
+
c:
|
41
|
+
ext: ['.java', '.c', '.cpp', '.cc']
|
42
|
+
comment:
|
43
|
+
open: '/*'
|
44
|
+
close: ' */\n\n'
|
45
|
+
prefix: ' * '
|
46
|
+
|
47
|
+
cpp:
|
48
|
+
ext: ['.cpp', '.cc', '.hh', '.hpp']
|
49
|
+
comment:
|
50
|
+
open: '//\n'
|
51
|
+
close: '//\n\n'
|
52
|
+
prefix: '// '
|
53
|
+
|
54
|
+
java:
|
55
|
+
ext: ['.java']
|
56
|
+
comment:
|
57
|
+
open: '/*\n'
|
58
|
+
close: ' */\n\n'
|
59
|
+
prefix: ' * '
|
60
|
+
|
61
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require "copyright_header/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "copyright-header"
|
8
|
+
s.version = CopyrightHeader::VERSION
|
9
|
+
s.authors = ["Erik Osterman"]
|
10
|
+
s.email = ["e@osterman.com"]
|
11
|
+
s.homepage = "https://github.com/osterman/copyright-header"
|
12
|
+
s.summary = %q{A utility to insert copyright headers into various types of source code files}
|
13
|
+
s.description = %q{A utility which is able to recursively insert and remove copyright headers from source code files based on file extensions.}
|
14
|
+
s.rubyforge_project = "copyright-header"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.extra_rdoc_files = ['README.md', 'LICENSE', 'AUTHORS', 'contrib/syntax.yml' ]
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module CopyrightHeader
|
4
|
+
class MissingArgumentException < Exception; end
|
5
|
+
|
6
|
+
class CommandLine
|
7
|
+
attr_accessor :options, :parser, :optparse
|
8
|
+
def initialize(options = {})
|
9
|
+
begin
|
10
|
+
@options = options
|
11
|
+
@options[:base_path] = File.expand_path File.dirname(__FILE__) + "/../../"
|
12
|
+
@optparse = OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: #{$0} options [file]"
|
14
|
+
|
15
|
+
@options[:dry_run] = false
|
16
|
+
opts.on( '-n', '--dry-run', 'Output the parsed files to STDOUT' ) do
|
17
|
+
@options[:dry_run] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on( '-o', '--output-dir DIR', 'Use DIR as output directory') do |dir|
|
21
|
+
@options[:output_dir] = dir + '/'
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on( '--license-file FILE', 'Use FILE as header' ) do|file|
|
25
|
+
@options[:license_file] = file
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on( '--license [' + Dir.glob(@options[:base_path] + '/licenses/*').map { |f| File.basename(f, '.erb') }.join('|') + ']', 'Use LICENSE as header' ) do|license|
|
29
|
+
@options[:license] = license
|
30
|
+
@options[:license_file] = @options[:base_path] + '/licenses/' + license + '.erb'
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on( '--copyright-software NAME', 'The common name for this piece of software (e.g. "Copyright Header")' ) do|name|
|
34
|
+
@options[:copyright_software] = name
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on( '--copyright-software-description DESC', 'The common name for this piece of software (e.g. "A utility to manipulate copyright headers on source code files")' ) do|desc|
|
38
|
+
@options[:copyright_software_description] = desc
|
39
|
+
end
|
40
|
+
|
41
|
+
@options[:copyright_holders] = []
|
42
|
+
opts.on( '--copyright-holder NAME', 'The common name for this piece of software (e.g. "Erik Osterman <e@osterman.com>"). Repeat argument for multiple names.' ) do|name|
|
43
|
+
@options[:copyright_holders] << name
|
44
|
+
end
|
45
|
+
|
46
|
+
@options[:copyright_years] = []
|
47
|
+
opts.on( '--copyright-year YEAR', 'The common name for this piece of software (e.g. "2012"). Repeat argument for multiple years.' ) do|year|
|
48
|
+
@options[:copyright_years] << year
|
49
|
+
end
|
50
|
+
|
51
|
+
@options[:word_wrap] = 80
|
52
|
+
opts.on( '-w', '--word-wrap LEN', 'Maximum number of characters per line for license (default: 80)' ) do |len|
|
53
|
+
@options[:word_wrap] = len.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on( '-a', '--add-path PATH', 'Recursively insert header in all files found in path' ) do |path|
|
57
|
+
@options[:add_path] = path
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on( '-r', '--remove-path PATH', 'Recursively remove header in all files found in path' ) do |path|
|
61
|
+
@options[:remove_path] = path
|
62
|
+
end
|
63
|
+
|
64
|
+
@options[:syntax] = @options[:base_path] + '/contrib/syntax.yml'
|
65
|
+
opts.on( '-c', '--syntax FILE', 'Syntax configuration file' ) do |path|
|
66
|
+
@options[:syntax] = path
|
67
|
+
end
|
68
|
+
|
69
|
+
opts.on( '-V', '--version', 'Display version information' ) do
|
70
|
+
puts "CopyrightHeader #{CopyrightHeader::VERSION}"
|
71
|
+
puts "Copyright (C) 2012 Erik Osterman <e@osterman.com>"
|
72
|
+
puts "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>"
|
73
|
+
puts "This is free software: you are free to change and redistribute it."
|
74
|
+
puts "There is NO WARRANTY, to the extent permitted by law."
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
79
|
+
puts opts
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@optparse.parse!
|
85
|
+
unless @options.has_key?(:license_file)
|
86
|
+
raise MissingArgumentException.new("Missing --license or --license-file argument")
|
87
|
+
end
|
88
|
+
|
89
|
+
unless File.file?(@options[:license_file])
|
90
|
+
raise MissingArgumentException.new("Invalid --license or --license-file argument. Cannot open #{@options[:license_file]}")
|
91
|
+
end
|
92
|
+
|
93
|
+
if @options[:license]
|
94
|
+
raise MissingArgumentException.new("Missing --copyright-software argument") if @options[:copyright_software].nil?
|
95
|
+
raise MissingArgumentException.new("Missing --copyright-software-description argument") if @options[:copyright_software_description].nil?
|
96
|
+
raise MissingArgumentException.new("Missing --copyright-holder argument") unless @options[:copyright_holders].length > 0
|
97
|
+
raise MissingArgumentException.new("Missing --copyright-year argument") unless @options[:copyright_years].length > 0
|
98
|
+
end
|
99
|
+
rescue MissingArgumentException => e
|
100
|
+
puts e.message
|
101
|
+
puts @optparse
|
102
|
+
exit (1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def execute
|
107
|
+
@parser = CopyrightHeader::Parser.new(@options)
|
108
|
+
@parser.execute
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
require 'erb'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module CopyrightHeader
|
7
|
+
class FileNotFoundException < Exception; end
|
8
|
+
|
9
|
+
class License
|
10
|
+
@lines = []
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
@lines = load_template.split(/(\n)/)
|
14
|
+
end
|
15
|
+
|
16
|
+
def word_wrap(text, max_width = nil)
|
17
|
+
max_width ||= @options[:word_wrap]
|
18
|
+
text.gsub(/(.{1,#{max_width}})(\s|\Z)/, "\\1\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_template
|
22
|
+
if File.exists?(@options[:license_file])
|
23
|
+
template = ::ERB.new File.new(@options[:license_file]).read, 0, '%<'
|
24
|
+
license = template.result(OpenStruct.new(@options).instance_eval { binding })
|
25
|
+
license += "\n" if license !~ /\n$/s
|
26
|
+
word_wrap(license)
|
27
|
+
else
|
28
|
+
raise FileNotFoundException.new("Unable to open #{file}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def format(comment_open = nil, comment_close = nil, comment_prefix = nil)
|
33
|
+
comment_open ||= ''
|
34
|
+
comment_close ||= ''
|
35
|
+
comment_prefix ||= ''
|
36
|
+
license = comment_open + @lines.map { |line| comment_prefix + line }.join() + comment_close
|
37
|
+
license.gsub!(/\\n/, "\n")
|
38
|
+
license
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Header
|
43
|
+
@file = nil
|
44
|
+
@contents = nil
|
45
|
+
@config = nil
|
46
|
+
|
47
|
+
def initialize(file, config)
|
48
|
+
@file = file
|
49
|
+
@contents = File.read(@file)
|
50
|
+
@config = config
|
51
|
+
end
|
52
|
+
|
53
|
+
def format(license)
|
54
|
+
license.format(@config[:comment]['open'], @config[:comment]['close'], @config[:comment]['prefix'])
|
55
|
+
end
|
56
|
+
|
57
|
+
def add(license)
|
58
|
+
if has_copyright?
|
59
|
+
puts "SKIP #{@file}; detected exisiting license"
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
copyright = self.format(license)
|
64
|
+
if copyright.nil?
|
65
|
+
puts "Copyright is nil"
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
text = ""
|
70
|
+
if @config.has_key?(:after) && @config[:after].instance_of?(Array)
|
71
|
+
copyright_written = false
|
72
|
+
lines = @contents.split(/\n/)
|
73
|
+
head = lines.shift(10)
|
74
|
+
while(head.size > 0)
|
75
|
+
line = head.shift
|
76
|
+
text += line + "\n"
|
77
|
+
@config[:after].each do |regex|
|
78
|
+
pattern = Regexp.new(regex)
|
79
|
+
if pattern.match(line)
|
80
|
+
text += copyright
|
81
|
+
copyright_written = true
|
82
|
+
break
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
if copyright_written
|
87
|
+
text += lines.join("\n")
|
88
|
+
else
|
89
|
+
text = copyright + text + lines.join("\n")
|
90
|
+
end
|
91
|
+
else
|
92
|
+
# Simply prepend text
|
93
|
+
text = copyright + @contents
|
94
|
+
end
|
95
|
+
return text
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove(license)
|
99
|
+
if has_copyright?
|
100
|
+
text = self.format(license)
|
101
|
+
@contents.gsub!(/#{Regexp.escape(text)}/, '')
|
102
|
+
@contents
|
103
|
+
else
|
104
|
+
puts "SKIP #{@file}; copyright not detected"
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def has_copyright?(lines = 10)
|
110
|
+
@contents.split(/\n/)[0..lines].select { |line| line =~ /[Cc]opyright|[Ll]icense/ }.length > 0
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Syntax
|
115
|
+
def initialize(config)
|
116
|
+
@config = {}
|
117
|
+
syntax = YAML.load_file(config)
|
118
|
+
syntax.each_value do |format|
|
119
|
+
format['ext'].each do |ext|
|
120
|
+
@config[ext] = {
|
121
|
+
:before => format['before'],
|
122
|
+
:after => format['after'],
|
123
|
+
:comment => format['comment']
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def ext(file)
|
130
|
+
File.extname(file)
|
131
|
+
end
|
132
|
+
|
133
|
+
def supported?(file)
|
134
|
+
@config.has_key? ext(file)
|
135
|
+
end
|
136
|
+
|
137
|
+
def header(file)
|
138
|
+
Header.new(file, @config[ext(file)])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Parser
|
143
|
+
attr_accessor :options
|
144
|
+
@syntax = nil
|
145
|
+
@license = nil
|
146
|
+
def initialize(options = {})
|
147
|
+
@options = options
|
148
|
+
@exclude = [ /^LICENSE(|\.txt)$/i, /^holders(|\.txt)$/i, /^README/, /^\./]
|
149
|
+
@license = License.new(:license_file => @options[:license_file],
|
150
|
+
:copyright_software => @options[:copyright_software],
|
151
|
+
:copyright_software_description => @options[:copyright_software_description],
|
152
|
+
:copyright_years => @options[:copyright_years],
|
153
|
+
:copyright_holders => @options[:copyright_holders],
|
154
|
+
:word_wrap => @options[:word_wrap])
|
155
|
+
@syntax = Syntax.new(@options[:syntax])
|
156
|
+
end
|
157
|
+
|
158
|
+
def execute
|
159
|
+
if @options.has_key?(:add_path)
|
160
|
+
add(@options[:add_path])
|
161
|
+
end
|
162
|
+
|
163
|
+
if @options.has_key?(:remove_path)
|
164
|
+
remove(@options[:remove_path])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def transform(method, path)
|
169
|
+
paths = []
|
170
|
+
if File.file?(path)
|
171
|
+
paths << path
|
172
|
+
else
|
173
|
+
paths << Dir.glob("#{path}/**/*")
|
174
|
+
end
|
175
|
+
|
176
|
+
puts paths.inspect
|
177
|
+
|
178
|
+
paths.flatten!
|
179
|
+
|
180
|
+
paths.each do |path|
|
181
|
+
if File.file?(path)
|
182
|
+
if @exclude.include? File.basename(path)
|
183
|
+
puts "SKIP #{path}; excluded"
|
184
|
+
next
|
185
|
+
end
|
186
|
+
|
187
|
+
if @syntax.supported?(path)
|
188
|
+
header = @syntax.header(path)
|
189
|
+
contents = header.send(method, @license)
|
190
|
+
if contents.nil?
|
191
|
+
puts "SKIP #{path}; failed to generate license"
|
192
|
+
else
|
193
|
+
write(path, contents)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
else
|
197
|
+
puts "SKIP #{path}; unsupported"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Add copyright header recursively
|
203
|
+
def add(dir)
|
204
|
+
transform(:add, dir)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Remove copyright header recursively
|
208
|
+
def remove(dir)
|
209
|
+
transform(:remove, dir)
|
210
|
+
end
|
211
|
+
|
212
|
+
def write(file, contents)
|
213
|
+
puts "UPDATE #{file}"
|
214
|
+
if @options[:dry_run] || @options[:output_dir].nil?
|
215
|
+
puts contents
|
216
|
+
else
|
217
|
+
dir = "#{@options[:output_dir]}/#{File.dirname(file)}"
|
218
|
+
FileUtils.mkpath dir unless File.directory?(dir)
|
219
|
+
output_path = @options[:output_dir] + file
|
220
|
+
f =File.new(output_path, 'w')
|
221
|
+
f.write(contents)
|
222
|
+
f.close
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|