copyright-header 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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,7 @@
1
+ require 'copyright_header/version'
2
+ require 'copyright_header/command_line'
3
+ require 'copyright_header/parser'
4
+
5
+ module CopyrightHeader
6
+ end
7
+
@@ -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
+