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/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
+