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