gem_lint 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/README.rdoc +56 -0
- data/lib/gem_lint/runner.rb +123 -0
- data/lib/gem_lint/strategies/abstract_strategy.rb +40 -0
- data/lib/gem_lint/strategies/bin_ends_with_rb_strategy.rb +39 -0
- data/lib/gem_lint/strategies/bin_without_shebang_strategy.rb +43 -0
- data/lib/gem_lint/strategies/capitals_in_name_strategy.rb +32 -0
- data/lib/gem_lint/strategies/changelog_strategy.rb +32 -0
- data/lib/gem_lint/strategies/contains_gem_file_strategy.rb +31 -0
- data/lib/gem_lint/strategies/csv_authors_strategy.rb +30 -0
- data/lib/gem_lint/strategies/csv_email_strategy.rb +30 -0
- data/lib/gem_lint/strategies/duplicate_authors_strategy.rb +29 -0
- data/lib/gem_lint/strategies/duplicate_email_strategy.rb +29 -0
- data/lib/gem_lint/strategies/pkg_dir_strategy.rb +31 -0
- data/lib/gem_lint/strategies/readme_strategy.rb +31 -0
- data/lib/gem_lint/strategies/require_matches_gemname_strategy.rb +42 -0
- data/lib/gem_lint/strategies/ruby_file_location_strategy.rb +38 -0
- data/lib/gem_lint/strategies/string_email_strategy.rb +29 -0
- data/lib/gem_lint/strategies/test_files_in_files_attribute_strategy.rb +37 -0
- data/lib/gem_lint/strategies/utf8_metadata_strategy.rb +26 -0
- data/lib/gem_lint.rb +25 -0
- data/lib/rubygems/commands/lint_command.rb +35 -0
- data/lib/rubygems_plugin.rb +3 -0
- metadata +150 -0
data/CHANGELOG
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= GemLint
|
2
|
+
|
3
|
+
Sanity check your rubygems for common issues. Missing readmes, bundled pkg
|
4
|
+
directories, encoding issues in the gemspec, etc.
|
5
|
+
|
6
|
+
Each test is classified as an error or warning. Errors are issues that (by my
|
7
|
+
reading) violate the documented gem specs. Warnings are either best practices
|
8
|
+
recommended by members of the community or arbitrary things that annoy me
|
9
|
+
personally.
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
gem install gem-lint
|
14
|
+
gem lint somefile-0.0.1.gem
|
15
|
+
|
16
|
+
=== API
|
17
|
+
|
18
|
+
To check a gem programmatically:
|
19
|
+
|
20
|
+
require "gem_lint"
|
21
|
+
|
22
|
+
runner = GemLint::Runner.new("widgets-1.0.gem")
|
23
|
+
puts runner.name
|
24
|
+
puts runner.version
|
25
|
+
puts runner.email
|
26
|
+
puts runner.tags
|
27
|
+
|
28
|
+
== Status
|
29
|
+
|
30
|
+
This a spike. It has few tests and is a proof of concept. Most of it was written at
|
31
|
+
a Railscamp and therefore is probably of suspect quality.
|
32
|
+
|
33
|
+
== Compatability
|
34
|
+
|
35
|
+
Rubygems 1.3.2 or greater is required, otherwise it's pure ruby should run on most
|
36
|
+
ruby VMs.
|
37
|
+
|
38
|
+
== Developing
|
39
|
+
|
40
|
+
To add new checks, create a new strategy in lib/gem_lint/strategies with a matching
|
41
|
+
spec in spec/strategies.
|
42
|
+
|
43
|
+
View other strategies for an idea of the required contract. There are existing
|
44
|
+
specs to ensure all strategies confirm to the contract, so keep the full spec
|
45
|
+
suit (using "rake") green.
|
46
|
+
|
47
|
+
Full source is on github at https://github.com/yob/gem-lint
|
48
|
+
|
49
|
+
== Ideas
|
50
|
+
|
51
|
+
* command for users to display extra info on a tag a gem failed
|
52
|
+
|
53
|
+
== Further Reading
|
54
|
+
|
55
|
+
* http://chneukirchen.github.com/rps/
|
56
|
+
* http://blog.segment7.net/articles/2010/11/15/how-to-name-gems
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module GemLint
|
4
|
+
class Runner
|
5
|
+
|
6
|
+
attr_reader :tags, :tags_with_level, :email, :name, :version
|
7
|
+
|
8
|
+
def initialize(filename)
|
9
|
+
raise ArgumentError, "'#{filename}' does not exist" unless File.file?(filename.to_s)
|
10
|
+
|
11
|
+
@filename = filename
|
12
|
+
init_vars
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def init_vars
|
18
|
+
unpack_gem
|
19
|
+
@tags = collect_tags
|
20
|
+
@tags_with_level = collect_tags_with_level
|
21
|
+
@email = spec ? spec.email : nil
|
22
|
+
@name = spec ? spec.name : nil
|
23
|
+
@version = spec ? spec.version.to_s : nil
|
24
|
+
cleanup
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns an array of symbols, each one indicating a test the provided gem
|
28
|
+
# failed. Unpacks the gem to a temporary location and cleans up after
|
29
|
+
# itself.
|
30
|
+
#
|
31
|
+
def collect_tags
|
32
|
+
if unpack_successful?
|
33
|
+
failed_strategies.map { |s|
|
34
|
+
s.tag
|
35
|
+
}
|
36
|
+
else
|
37
|
+
["unpack-failed"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def collect_tags_with_level
|
42
|
+
if unpack_successful?
|
43
|
+
failed_strategies.map { |s|
|
44
|
+
"#{s.level_char}: #{s.tag}"
|
45
|
+
}.sort
|
46
|
+
else
|
47
|
+
["E: unpack-failed"]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def failed_strategies
|
52
|
+
if unpack_successful?
|
53
|
+
@failed_strategies ||= GemLint.strategies.map { |s|
|
54
|
+
s.new(visitor_args)
|
55
|
+
}.select { |s|
|
56
|
+
s.fail?
|
57
|
+
}
|
58
|
+
else
|
59
|
+
@failed_strategies ||= []
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def visitor_args
|
64
|
+
@visitor_args ||= {
|
65
|
+
:filename => @filename,
|
66
|
+
:data_path => data_path,
|
67
|
+
:metadata_path => metadata_file
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def unpack_successful?
|
72
|
+
File.directory?(data_path) && File.file?(metadata_file)
|
73
|
+
end
|
74
|
+
|
75
|
+
def data_path
|
76
|
+
@data_path ||= File.join(unpack_path, "data")
|
77
|
+
end
|
78
|
+
|
79
|
+
def data_file
|
80
|
+
@data_file ||= File.join(unpack_path, "data.tar.gz")
|
81
|
+
end
|
82
|
+
|
83
|
+
def metadata_gz_file
|
84
|
+
@metadata_gz_file ||= File.join(unpack_path, "metadata.gz")
|
85
|
+
end
|
86
|
+
|
87
|
+
def metadata_file
|
88
|
+
@metadata_file ||= File.join(unpack_path, "metadata")
|
89
|
+
end
|
90
|
+
|
91
|
+
def spec
|
92
|
+
if @spec || File.file?(metadata_file)
|
93
|
+
@spec ||= YAML.load_file(metadata_file)
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def unpack_path
|
100
|
+
@unpack_path ||= File.join(Dir.tmpdir, rand(100000).to_s)
|
101
|
+
end
|
102
|
+
|
103
|
+
# TODO: update this to use native ruby untar
|
104
|
+
def unpack_gem
|
105
|
+
Dir.mkdir(unpack_path)
|
106
|
+
Dir.mkdir(data_path)
|
107
|
+
|
108
|
+
`tar -xkC #{unpack_path} -f #{@filename} > /dev/null 2>&1`
|
109
|
+
`tar -xzkC #{data_path} -f #{data_file} > /dev/null 2>&1`
|
110
|
+
`gunzip #{metadata_file} > /dev/null 2>&1`
|
111
|
+
|
112
|
+
FileUtils.remove_entry_secure(data_file) if File.file?(data_file)
|
113
|
+
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
def cleanup
|
118
|
+
return unless File.directory?(unpack_path)
|
119
|
+
|
120
|
+
FileUtils.remove_entry_secure(unpack_path)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module GemLint
|
4
|
+
module Strategies
|
5
|
+
class AbstractStrategy
|
6
|
+
attr_reader :filename, :path, :metadata_path
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
@filename = args[:filename]
|
10
|
+
@path = args[:data_path]
|
11
|
+
@metadata_path = args[:metadata_path]
|
12
|
+
|
13
|
+
raise ArgumentError, "#{@path} is not a directory" unless File.directory?(@path)
|
14
|
+
raise ArgumentError, "#{@metadata_path} is not a file" unless File.file?(@metadata_path)
|
15
|
+
raise ArgumentError, ":filename must be a filename ending in gem" unless @filename.to_s[-4,4] == ".gem"
|
16
|
+
end
|
17
|
+
|
18
|
+
def level_char
|
19
|
+
self.level.to_s[0,1].upcase
|
20
|
+
end
|
21
|
+
|
22
|
+
def description
|
23
|
+
raise "this should be implemented by concrete subclasses"
|
24
|
+
end
|
25
|
+
|
26
|
+
def fail?
|
27
|
+
raise "this should be implemented by concrete subclasses"
|
28
|
+
end
|
29
|
+
|
30
|
+
def level
|
31
|
+
raise "this should be implemented by concrete subclasses"
|
32
|
+
end
|
33
|
+
|
34
|
+
def tag
|
35
|
+
raise "this should be implemented by concrete subclasses"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class BinEndsWithRbStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"A file in bin/ ends in .rb"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"bin-ends-with-rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
bin_files.any? { |path| path[-3,3] == ".rb" }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def bin_files
|
24
|
+
all_files.select { |path|
|
25
|
+
path[0,4] == "bin/" && File.file?(self.path + "/" + path)
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_files
|
30
|
+
paths = []
|
31
|
+
Find.find(self.path) { |path| paths << path }
|
32
|
+
paths.map { |path|
|
33
|
+
path.gsub(self.path + "/","")
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class BinWithoutShebangStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"A file in bin/ doesn't have a shebang on the first line"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"bin-without-shebang"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
bin_files.any? { |path|
|
19
|
+
data = File.open(self.path + "/" + path) { |f| f.read }
|
20
|
+
first_line = data.split("\n")[0]
|
21
|
+
!first_line.to_s.include?("env ruby")
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def bin_files
|
28
|
+
all_files.select { |path|
|
29
|
+
path[0,4] == "bin/" && File.file?(self.path + "/" + path)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def all_files
|
34
|
+
paths = []
|
35
|
+
Find.find(self.path) { |path| paths << path }
|
36
|
+
paths.map { |path|
|
37
|
+
path.gsub(self.path + "/","")
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module GemLint
|
4
|
+
module Strategies
|
5
|
+
class CapitalsInNameStrategy < AbstractStrategy
|
6
|
+
|
7
|
+
def description
|
8
|
+
"It's strongly recommended that your gem name be all lower case"
|
9
|
+
end
|
10
|
+
|
11
|
+
def tag
|
12
|
+
:"capitals-in-name"
|
13
|
+
end
|
14
|
+
|
15
|
+
def level
|
16
|
+
:warning
|
17
|
+
end
|
18
|
+
|
19
|
+
def fail?
|
20
|
+
spec.name.match(/[A-Z]/)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def spec
|
26
|
+
@spec ||= YAML.load(File.read(@metadata_path))
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class ChangelogStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Gem contains no changelog or history file"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"no-changelog-or-history"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
root_files.none? do |filename|
|
19
|
+
dfile = filename.downcase
|
20
|
+
dfile.include?("changelog") || dfile.include?("history")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def root_files
|
27
|
+
Dir.entries(self.path)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class ContainsGemFileStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Gem contains at least 1 other gem file"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"contains-gem-file"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
spec.files.any? { |filename|
|
19
|
+
filename.match(/\A.*\.gem\Z/)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def spec
|
26
|
+
@spec ||= YAML.load(File.read(@metadata_path))
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class CsvAuthorsStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Authors field in spec uses a comma or semicolon to specify multiple authors instead of an array"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"csv-authors"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:error
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
yaml.authors.is_a?(Array) &&
|
19
|
+
(yaml.authors.first.include?(",") || yaml.authors.first.include?(";"))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def yaml
|
25
|
+
@yaml ||= YAML.load(File.read(@metadata_path))
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class CsvEmailStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Email field in spec uses CSV to specify multiple emails instead of an array"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"csv-email"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:error
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
(yaml.email.is_a?(String) && (yaml.email.include?(",") || yaml.email.include?(";"))) ||
|
19
|
+
(yaml.email.is_a?(Array) && (yaml.email.first.include?(",") || yaml.email.first.include?(";")))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def yaml
|
25
|
+
@yaml ||= YAML.load(File.read(@metadata_path))
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class DuplicateAuthorsStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Authors field in spec should have no duplicate values"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"duplicate-authors"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
yaml.authors.is_a?(Array) && yaml.authors.uniq.size != yaml.authors.size
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def yaml
|
24
|
+
@yaml ||= YAML.load(File.read(@metadata_path))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class DuplicateEmailStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Email field in spec should have no duplicate values"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"csv-email"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
yaml.email.is_a?(Array) && yaml.email.uniq.size != yaml.email.size
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def yaml
|
24
|
+
@yaml ||= YAML.load(File.read(@metadata_path))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class PkgDirStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Gem contains a directory in the root called pkg"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"pkg-dir"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
root_files.any? { |filename|
|
19
|
+
filename.match(/pkg.*/)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def root_files
|
26
|
+
Dir.entries(self.path)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class ReadmeStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Gem contains no readme file"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"no-readme"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
root_files.none? do |filename|
|
19
|
+
filename.downcase.include?("readme")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def root_files
|
26
|
+
Dir.entries(self.path)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module GemLint
|
4
|
+
module Strategies
|
5
|
+
class RequireMatchesGemnameStrategy < AbstractStrategy
|
6
|
+
|
7
|
+
def description
|
8
|
+
"Gem cannot be loaded by require '#{preferred_basename}'"
|
9
|
+
end
|
10
|
+
|
11
|
+
def tag
|
12
|
+
:"require-doesnt-match-gemname"
|
13
|
+
end
|
14
|
+
|
15
|
+
def level
|
16
|
+
:warning
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: does thi need to be beefed up to recognise extensions
|
20
|
+
#
|
21
|
+
def fail?
|
22
|
+
!spec.files.include?(preferred_filename)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def preferred_basename
|
28
|
+
"lib/" + spec.name.tr("-","/")
|
29
|
+
end
|
30
|
+
|
31
|
+
def preferred_filename
|
32
|
+
preferred_basename + ".rb"
|
33
|
+
end
|
34
|
+
|
35
|
+
def spec
|
36
|
+
@spec ||= YAML.load(File.read(@metadata_path))
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module GemLint
|
4
|
+
module Strategies
|
5
|
+
class RubyFileLocationStrategy < AbstractStrategy
|
6
|
+
|
7
|
+
def description
|
8
|
+
"Gem contains ruby files outside of lib, test and spec"
|
9
|
+
end
|
10
|
+
|
11
|
+
def tag
|
12
|
+
:"ruby-files-outside-lib-test-spec"
|
13
|
+
end
|
14
|
+
|
15
|
+
def level
|
16
|
+
:warning
|
17
|
+
end
|
18
|
+
|
19
|
+
def fail?
|
20
|
+
all_files.any? do |filename|
|
21
|
+
filename[0,3] != "lib" && filename[0,4] != "spec" &&
|
22
|
+
filename[0,4] != "test" && filename[-3,3] == ".rb"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def all_files
|
29
|
+
paths = []
|
30
|
+
Find.find(self.path) { |path| paths << path }
|
31
|
+
paths.map { |path|
|
32
|
+
path.gsub(self.path + "/","")
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class StringEmailStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Email field in spec is a String instead of an Array"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"string-email"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:error
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
yaml.email.is_a?(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def yaml
|
24
|
+
@yaml ||= YAML.load(File.read(@metadata_path))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'find'
|
2
|
+
|
3
|
+
module GemLint
|
4
|
+
module Strategies
|
5
|
+
class TestFilesInFilesAttributeStrategy < AbstractStrategy
|
6
|
+
|
7
|
+
def description
|
8
|
+
"Gem metadata includes test files in files attribute. Use test_files instead"
|
9
|
+
end
|
10
|
+
|
11
|
+
def tag
|
12
|
+
:"test-files-in-files-attribute"
|
13
|
+
end
|
14
|
+
|
15
|
+
def level
|
16
|
+
:warning
|
17
|
+
end
|
18
|
+
|
19
|
+
def fail?
|
20
|
+
orig_files.any? { |filename|
|
21
|
+
filename[-8,8] == "_spec.rb" || filename[-8,8] == "_test.rb"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def orig_files
|
28
|
+
@orig_files ||= yaml.files - yaml.test_files
|
29
|
+
end
|
30
|
+
|
31
|
+
def yaml
|
32
|
+
@yaml ||= YAML.load(File.read(@metadata_path))
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module GemLint
|
2
|
+
module Strategies
|
3
|
+
class Utf8MetadataStrategy < AbstractStrategy
|
4
|
+
|
5
|
+
def description
|
6
|
+
"Ensure the gem metadata is all utf8 encoded"
|
7
|
+
end
|
8
|
+
|
9
|
+
def tag
|
10
|
+
:"utf8-metadata"
|
11
|
+
end
|
12
|
+
|
13
|
+
def level
|
14
|
+
:error
|
15
|
+
end
|
16
|
+
|
17
|
+
def fail?
|
18
|
+
File.read(@metadata_path).unpack("U*")
|
19
|
+
false
|
20
|
+
rescue
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/gem_lint.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# stdlib
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
# our own code
|
8
|
+
require 'gem_lint/runner'
|
9
|
+
|
10
|
+
# dynamically load all available strategies
|
11
|
+
strategies_path = File.dirname(__FILE__) + "/gem_lint/strategies/**/*.rb"
|
12
|
+
Dir[strategies_path].sort.each {|f| require f }
|
13
|
+
|
14
|
+
module GemLint
|
15
|
+
|
16
|
+
# return an array of all strategy classes
|
17
|
+
#
|
18
|
+
def self.strategies
|
19
|
+
GemLint::Strategies.constants.sort.select { |class_name|
|
20
|
+
class_name != "AbstractStrategy"
|
21
|
+
}.map { |class_name|
|
22
|
+
GemLint::Strategies.const_get(class_name)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems/command'
|
2
|
+
require 'gem_lint'
|
3
|
+
|
4
|
+
class Gem::Commands::LintCommand < Gem::Command
|
5
|
+
def description # :nodoc:
|
6
|
+
'Check a gem file for common mistakes and errors'
|
7
|
+
end
|
8
|
+
|
9
|
+
def arguments # :nodoc:
|
10
|
+
"GEM built gem to check"
|
11
|
+
end
|
12
|
+
|
13
|
+
def usage # :nodoc:
|
14
|
+
"#{program_name} GEM"
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
super 'lint', description
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute
|
22
|
+
runner = GemLint::Runner.new(get_one_gem_name)
|
23
|
+
if runner.tags.empty?
|
24
|
+
puts "No test failures!"
|
25
|
+
puts
|
26
|
+
else
|
27
|
+
runner.tags_with_level.each do |tag|
|
28
|
+
puts "- #{tag}"
|
29
|
+
end
|
30
|
+
puts
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gem_lint
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Healy
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-29 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rake
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rcov
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: roodi
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rspec
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 2
|
74
|
+
- 0
|
75
|
+
version: "2.0"
|
76
|
+
type: :development
|
77
|
+
version_requirements: *id004
|
78
|
+
description: Adds a 'lint' command rubygems that will pront a list of possible errors in a gem
|
79
|
+
email:
|
80
|
+
- james@yob.id.au
|
81
|
+
executables: []
|
82
|
+
|
83
|
+
extensions: []
|
84
|
+
|
85
|
+
extra_rdoc_files: []
|
86
|
+
|
87
|
+
files:
|
88
|
+
- lib/gem_lint.rb
|
89
|
+
- lib/rubygems_plugin.rb
|
90
|
+
- lib/rubygems/commands/lint_command.rb
|
91
|
+
- lib/gem_lint/runner.rb
|
92
|
+
- lib/gem_lint/strategies/require_matches_gemname_strategy.rb
|
93
|
+
- lib/gem_lint/strategies/utf8_metadata_strategy.rb
|
94
|
+
- lib/gem_lint/strategies/readme_strategy.rb
|
95
|
+
- lib/gem_lint/strategies/changelog_strategy.rb
|
96
|
+
- lib/gem_lint/strategies/duplicate_authors_strategy.rb
|
97
|
+
- lib/gem_lint/strategies/bin_ends_with_rb_strategy.rb
|
98
|
+
- lib/gem_lint/strategies/bin_without_shebang_strategy.rb
|
99
|
+
- lib/gem_lint/strategies/csv_email_strategy.rb
|
100
|
+
- lib/gem_lint/strategies/abstract_strategy.rb
|
101
|
+
- lib/gem_lint/strategies/pkg_dir_strategy.rb
|
102
|
+
- lib/gem_lint/strategies/capitals_in_name_strategy.rb
|
103
|
+
- lib/gem_lint/strategies/test_files_in_files_attribute_strategy.rb
|
104
|
+
- lib/gem_lint/strategies/string_email_strategy.rb
|
105
|
+
- lib/gem_lint/strategies/contains_gem_file_strategy.rb
|
106
|
+
- lib/gem_lint/strategies/ruby_file_location_strategy.rb
|
107
|
+
- lib/gem_lint/strategies/duplicate_email_strategy.rb
|
108
|
+
- lib/gem_lint/strategies/csv_authors_strategy.rb
|
109
|
+
- README.rdoc
|
110
|
+
- CHANGELOG
|
111
|
+
has_rdoc: true
|
112
|
+
homepage: http://github.com/yob/gem-lint
|
113
|
+
licenses: []
|
114
|
+
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options:
|
117
|
+
- --title
|
118
|
+
- Gem Lint
|
119
|
+
- --line-numbers
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
hash: 3
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
hash: 31
|
137
|
+
segments:
|
138
|
+
- 1
|
139
|
+
- 3
|
140
|
+
- 2
|
141
|
+
version: 1.3.2
|
142
|
+
requirements: []
|
143
|
+
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 1.3.7
|
146
|
+
signing_key:
|
147
|
+
specification_version: 3
|
148
|
+
summary: Check rubygem files for common mistakes and errors
|
149
|
+
test_files: []
|
150
|
+
|