gemnasium-parser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +49 -0
- data/Rakefile +9 -0
- data/gemnasium-parser.gemspec +19 -0
- data/lib/gemnasium/parser.rb +17 -0
- data/lib/gemnasium/parser/configuration.rb +11 -0
- data/lib/gemnasium/parser/gemfile.rb +109 -0
- data/lib/gemnasium/parser/gemspec.rb +38 -0
- data/lib/gemnasium/parser/patterns.rb +63 -0
- data/lib/gemnasium/parser/version.rb +5 -0
- data/spec/gemnasium/parser/gemfile_spec.rb +206 -0
- data/spec/gemnasium/parser/gemspec_spec.rb +128 -0
- data/spec/gemnasium/parser_spec.rb +25 -0
- data/spec/spec_helper.rb +1 -0
- metadata +87 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Steve Richert
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Gemnasium::Parser [![Build Status](https://secure.travis-ci.org/laserlemon/gemnasium-parser.png)](http://travis-ci.org/laserlemon/gemnasium-parser) [![Dependency Status](https://gemnasium.com/laserlemon/gemnasium-parser.png)](https://gemnasium.com/laserlemon/gemnasium-parser)
|
2
|
+
|
3
|
+
The [Gemnasium](https://gemnasium.com/) parser determines gem dependencies from gemfiles and gemspecs, without evaluating the Ruby.
|
4
|
+
|
5
|
+
## Why?
|
6
|
+
|
7
|
+
[Bundler](http://gembundler.com/) is wonderful. It takes your gemfile and your gemspec (both just Ruby) and evaluates them, determining your gem dependencies. This works great locally and even on your production server… but only because you can be trusted!
|
8
|
+
|
9
|
+
An untrustworthy character could put some pretty nasty stuff in a gemfile. If Gemnasium were to blindly evaluate that Ruby on its servers, havoc would ensue.
|
10
|
+
|
11
|
+
### Solution #1
|
12
|
+
|
13
|
+
If evaluating Ruby is so dangerous, just sandbox it! [Travis CI](http://travis-ci.org/) runs its builds inside isolated environments built with [Vagrant](http://vagrantup.com/). That way, if anything goes awry, it’s in a controlled environment.
|
14
|
+
|
15
|
+
This is entirely possible with Gemnasium, but it’s impractical. Gemfiles often `require` other files in the repository. So to evaluate a gemfile, Gemnasium needs to clone the entire repo. That’s an expensive operation when only a couple files determine the dependencies.
|
16
|
+
|
17
|
+
### Solution #2
|
18
|
+
|
19
|
+
Parse Ruby like Ruby parses Ruby.
|
20
|
+
|
21
|
+
Ruby 1.9 includes a library called Ripper. Ripper is a Ruby parsing library that can break down a gemfile or gemspec into bite-sized chunks, without evaluating the source. Then it can be searched for just the methods that matter.
|
22
|
+
|
23
|
+
The problem is that it’s hard to make heads or tails from Ripper’s output, at least for me. I could see the Gemnasium parser one day moving to this strategy. But not today.
|
24
|
+
|
25
|
+
### Third try’s the charm
|
26
|
+
|
27
|
+
If we can’t evaluate the Ruby and Ripper’s output is unmanageable, how else can we find patterns in a gemfile or gemspec and get usable output?
|
28
|
+
|
29
|
+
Regular expressions!
|
30
|
+
|
31
|
+
The Gemnasium parser, for both gemfiles and gemspecs, is based on a number of Ruby regular expressions. These patterns match `gem` method calls in gemfiles and `add_dependency` calls in gemspecs.
|
32
|
+
|
33
|
+
For a more comprehensive list of its abilities, see the [specs](https://github.com/laserlemon/gemnasium-parser/tree/master/spec).
|
34
|
+
|
35
|
+
## Contributing <a name="contributing"></a>
|
36
|
+
|
37
|
+
1. Fork it
|
38
|
+
2. Create your branch (`git checkout -b my-bugfix`)
|
39
|
+
3. Commit your changes (`git commit -am "Fix my bug"`)
|
40
|
+
4. Push your branch (`git push origin my-bugfix`)
|
41
|
+
5. Send a pull request
|
42
|
+
|
43
|
+
## Problems?
|
44
|
+
|
45
|
+
If you have a gemfile or gemspec that the Gemnasium parser screws up…
|
46
|
+
|
47
|
+
1. Boil it down to its simplest problematic form
|
48
|
+
2. Write a failing spec
|
49
|
+
3. See [Contributing](#contributing)
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/gemnasium/parser/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = "Steve Richert"
|
6
|
+
gem.email = "steve.richert@gmail.com"
|
7
|
+
gem.description = "Safely parse Gemfiles and gemspecs"
|
8
|
+
gem.summary = "Safely parse Gemfiles and gemspecs"
|
9
|
+
gem.homepage = "https://github.com/laserlemon/gemnasium-parser"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
gem.name = "gemnasium-parser"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = Gemnasium::Parser::VERSION
|
16
|
+
|
17
|
+
gem.add_development_dependency "rake", ">= 0.8.7"
|
18
|
+
gem.add_development_dependency "rspec", "~> 2.7"
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "gemnasium/parser/configuration"
|
2
|
+
require "gemnasium/parser/gemfile"
|
3
|
+
require "gemnasium/parser/gemspec"
|
4
|
+
|
5
|
+
module Gemnasium
|
6
|
+
module Parser
|
7
|
+
extend Configuration
|
8
|
+
|
9
|
+
def self.gemfile(content)
|
10
|
+
Gemnasium::Parser::Gemfile.new(content)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.gemspec(content)
|
14
|
+
Gemnasium::Parser::Gemspec.new(content)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "bundler"
|
2
|
+
require "gemnasium/parser/patterns"
|
3
|
+
|
4
|
+
module Gemnasium
|
5
|
+
module Parser
|
6
|
+
class Gemfile
|
7
|
+
attr_reader :content
|
8
|
+
|
9
|
+
def initialize(content)
|
10
|
+
@content = content
|
11
|
+
end
|
12
|
+
|
13
|
+
def dependencies
|
14
|
+
@dependencies ||= [].tap do |deps|
|
15
|
+
gem_matches.each do |match|
|
16
|
+
dep = dependency(match)
|
17
|
+
deps << dep if dep
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def gemspec
|
23
|
+
@gemspec = if gemspec_match
|
24
|
+
opts = Patterns.options(gemspec_match["opts"])
|
25
|
+
path = opts["path"]
|
26
|
+
name = opts["name"] || "*"
|
27
|
+
File.join(*[path, "#{name}.gemspec"].compact)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def gemspec?
|
32
|
+
!!gemspec
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def gem_matches
|
37
|
+
@gem_matches ||= matches(Patterns::GEM_CALL)
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches(pattern)
|
41
|
+
[].tap{|m| content.scan(pattern){ m << Regexp.last_match } }
|
42
|
+
end
|
43
|
+
|
44
|
+
def dependency(match)
|
45
|
+
opts = Patterns.options(match["opts"])
|
46
|
+
return nil if exclude?(match, opts)
|
47
|
+
clean!(match, opts)
|
48
|
+
name, reqs = match["name"], [match["req1"], match["req2"]].compact
|
49
|
+
Bundler::Dependency.new(name, reqs, opts).tap do |dep|
|
50
|
+
line = content.slice(0, match.begin(0)).count("\n") + 1
|
51
|
+
dep.instance_variable_set(:@line, line)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def groups(match)
|
56
|
+
group = group_matches.detect{|m| in_block?(match, m) }
|
57
|
+
group && Patterns.values(group[:grps])
|
58
|
+
end
|
59
|
+
|
60
|
+
def in_block?(inner, outer)
|
61
|
+
outer.begin(:blk) <= inner.begin(0) && outer.end(:blk) >= inner.end(0)
|
62
|
+
end
|
63
|
+
|
64
|
+
def group_matches
|
65
|
+
@group_matches ||= matches(Patterns::GROUP_CALL)
|
66
|
+
end
|
67
|
+
|
68
|
+
def exclude?(match, opts)
|
69
|
+
git?(match, opts) || path?(match, opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
def git?(match, opts)
|
73
|
+
opts["git"] || in_git_block?(match)
|
74
|
+
end
|
75
|
+
|
76
|
+
def in_git_block?(match)
|
77
|
+
git_matches.any?{|m| in_block?(match, m) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def git_matches
|
81
|
+
@git_matches ||= matches(Patterns::GIT_CALL)
|
82
|
+
end
|
83
|
+
|
84
|
+
def path?(match, opts)
|
85
|
+
opts["path"] || in_path_block?(match)
|
86
|
+
end
|
87
|
+
|
88
|
+
def in_path_block?(match)
|
89
|
+
path_matches.any?{|m| in_block?(match, m) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def path_matches
|
93
|
+
@path_matches ||= matches(Patterns::PATH_CALL)
|
94
|
+
end
|
95
|
+
|
96
|
+
def clean!(match, opts)
|
97
|
+
opts["group"] ||= groups(match)
|
98
|
+
groups = Array(opts["group"]).flatten.compact
|
99
|
+
runtime = groups.empty? || !(groups & Parser.runtime_groups).empty?
|
100
|
+
opts["type"] ||= runtime ? :runtime : :development
|
101
|
+
end
|
102
|
+
|
103
|
+
def gemspec_match
|
104
|
+
return @gemspec_match if defined?(@gemspec_match)
|
105
|
+
@gemspec_match = content.match(Patterns::GEMSPEC_CALL)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Gemnasium
|
2
|
+
module Parser
|
3
|
+
class Gemspec
|
4
|
+
attr_reader :content
|
5
|
+
|
6
|
+
def initialize(content)
|
7
|
+
@content = content
|
8
|
+
end
|
9
|
+
|
10
|
+
def dependencies
|
11
|
+
@dependencies ||= [].tap do |deps|
|
12
|
+
runtime_matches.each do |match|
|
13
|
+
dep = dependency(match)
|
14
|
+
deps << dep
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def runtime_matches
|
21
|
+
@runtime_matches ||= matches(Patterns::RUNTIME_CALL)
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches(pattern)
|
25
|
+
[].tap{|m| content.scan(pattern){ m << Regexp.last_match } }
|
26
|
+
end
|
27
|
+
|
28
|
+
def dependency(match)
|
29
|
+
name, reqs = match["name"], [match["req1"], match["req2"]].compact
|
30
|
+
type = match["type"] =~ /development/ ? :development : :runtime
|
31
|
+
Bundler::Dependency.new(name, reqs, "type" => type).tap do |dep|
|
32
|
+
line = content.slice(0, match.begin(0)).count("\n") + 1
|
33
|
+
dep.instance_variable_set(:@line, line)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Gemnasium
|
2
|
+
module Parser
|
3
|
+
module Patterns
|
4
|
+
GEM_NAME = /[a-zA-Z0-9\-_]+/
|
5
|
+
|
6
|
+
MATCHER = /(?:=|!=|>|<|>=|<=|~>)/
|
7
|
+
VERSION = /[0-9]+(?:\.[a-zA-Z0-9]+)*/
|
8
|
+
REQUIREMENT = /\s*(?:#{MATCHER}\s*)?#{VERSION}\s*/
|
9
|
+
REQUIREMENT_LIST = /(?<qr1>["'])(?<req1>#{REQUIREMENT})\k<qr1>(?:\s*,\s*(?<qr2>["'])(?<req2>#{REQUIREMENT})\k<qr2>)?/
|
10
|
+
REQUIREMENTS = /(?:#{REQUIREMENT_LIST}|\[\s*#{REQUIREMENT_LIST}\s*\])/
|
11
|
+
|
12
|
+
KEY = /(?::\w+|:?"\w+"|:?'\w+')/
|
13
|
+
SYMBOL = /(?::\w+|:"[^"#]+"|:'[^']+')/
|
14
|
+
STRING = /(?:"[^"#]*"|'[^']*')/
|
15
|
+
BOOLEAN = /(?:true|false)/
|
16
|
+
NIL = /nil/
|
17
|
+
ELEMENT = /(?:#{SYMBOL}|#{STRING})/
|
18
|
+
ARRAY = /\[(?:#{ELEMENT}(?:\s*,\s*#{ELEMENT})*)?\]/
|
19
|
+
VALUE = /(?:#{BOOLEAN}|#{NIL}|#{ELEMENT}|#{ARRAY}|)/
|
20
|
+
PAIR = /(?:(#{KEY})\s*=>\s*(#{VALUE})|(\w+):\s+(#{VALUE}))/
|
21
|
+
OPTIONS = /#{PAIR}(?:\s*,\s*#{PAIR})*/
|
22
|
+
|
23
|
+
GEM_CALL = /^\s*gem\s+(?<q1>["'])(?<name>#{GEM_NAME})\k<q1>(?:\s*,\s*#{REQUIREMENT_LIST})?(?:\s*,\s*(?<opts>#{OPTIONS}))?\s*$/
|
24
|
+
|
25
|
+
SYMBOLS = /#{SYMBOL}(\s*,\s*#{SYMBOL})*/
|
26
|
+
GROUP_CALL = /^(?<i1>\s*)group\s+(?<grps>#{SYMBOLS})\s+do\s*?\n(?<blk>.*?)\n^\k<i1>end\s*$/m
|
27
|
+
|
28
|
+
GIT_CALL = /^(?<i1>\s*)git\s+.*?\n(?<blk>.*?)\n^\k<i1>end\s*$/m
|
29
|
+
|
30
|
+
PATH_CALL = /^(?<i1>\s*)path\s+.*?\n(?<blk>.*?)\n^\k<i1>end\s*$/m
|
31
|
+
|
32
|
+
GEMSPEC_CALL = /^\s*gemspec(?:\s+(?<opts>#{OPTIONS}))?\s*$/
|
33
|
+
|
34
|
+
RUNTIME_CALL = /^\s*\w+\.add(?<type>_runtime|_development)?_dependency\s+(?<q1>["'])(?<name>#{GEM_NAME})\k<q1>(?:\s*,\s*#{REQUIREMENTS})?\s*$/
|
35
|
+
|
36
|
+
def self.options(string)
|
37
|
+
{}.tap do |hash|
|
38
|
+
return hash unless string
|
39
|
+
pairs = Hash[*string.match(OPTIONS).captures.compact]
|
40
|
+
pairs.each{|k,v| hash[key(k)] = value(v) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.key(string)
|
45
|
+
string.tr(%(:"'), "")
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.value(string)
|
49
|
+
case string
|
50
|
+
when ARRAY then values(string.tr("[]", ""))
|
51
|
+
when SYMBOL then string.tr(%(:"'), "").to_sym
|
52
|
+
when STRING then string.tr(%("'), "")
|
53
|
+
when BOOLEAN then string == "true"
|
54
|
+
when NIL then nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.values(string)
|
59
|
+
string.strip.split(/\s*,\s*/).map{|v| value(v) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Gemnasium::Parser::Gemfile do
|
4
|
+
def content(string)
|
5
|
+
@content ||= begin
|
6
|
+
indent = string.scan(/^[ \t]*(?=\S)/)
|
7
|
+
n = indent ? indent.size : 0
|
8
|
+
string.gsub(/^[ \t]{#{n}}/, "")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def gemfile
|
13
|
+
@gemfile ||= Gemnasium::Parser::Gemfile.new(@content)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dependencies
|
17
|
+
@dependencies ||= gemfile.dependencies
|
18
|
+
end
|
19
|
+
|
20
|
+
def dependency
|
21
|
+
dependencies.size.should == 1
|
22
|
+
dependencies.first
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
@content = @gemfile = @dependencies = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "parses double quotes" do
|
30
|
+
content(%(gem "rake", ">= 0.8.7"))
|
31
|
+
dependency.name.should == "rake"
|
32
|
+
dependency.requirement.should == ">= 0.8.7"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "parses single quotes" do
|
36
|
+
content(%(gem 'rake', '>= 0.8.7'))
|
37
|
+
dependency.name.should == "rake"
|
38
|
+
dependency.requirement.should == ">= 0.8.7"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "ignores mixed quotes" do
|
42
|
+
content(%(gem "rake', ">= 0.8.7"))
|
43
|
+
dependencies.size.should == 0
|
44
|
+
end
|
45
|
+
|
46
|
+
it "parses non-requirement gems" do
|
47
|
+
content(%(gem "rake"))
|
48
|
+
dependency.name.should == "rake"
|
49
|
+
dependency.requirement.should == ">= 0"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "parses multi-requirement gems" do
|
53
|
+
content(%(gem "rake", ">= 0.8.7", "<= 0.9.2"))
|
54
|
+
dependency.name.should == "rake"
|
55
|
+
dependency.requirement.as_list.should == ["<= 0.9.2", ">= 0.8.7"]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "parses gems with options" do
|
59
|
+
content(%(gem "rake", ">= 0.8.7", :require => false))
|
60
|
+
dependency.name.should == "rake"
|
61
|
+
dependency.requirement.should == ">= 0.8.7"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "listens for gemspecs" do
|
65
|
+
content(%(gemspec))
|
66
|
+
gemfile.should be_gemspec
|
67
|
+
gemfile.gemspec.should == "*.gemspec"
|
68
|
+
reset
|
69
|
+
content(%(gem "rake"))
|
70
|
+
gemfile.should_not be_gemspec
|
71
|
+
gemfile.gemspec.should be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "parses gemspecs with a name option" do
|
75
|
+
content(%(gemspec :name => "gemnasium-parser"))
|
76
|
+
gemfile.gemspec.should == "gemnasium-parser.gemspec"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "parses gemspecs with a path option" do
|
80
|
+
content(%(gemspec :path => "lib/gemnasium"))
|
81
|
+
gemfile.gemspec.should == "lib/gemnasium/*.gemspec"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "parses gemspecs with name and path options" do
|
85
|
+
content(%(gemspec :name => "parser", :path => "lib/gemnasium"))
|
86
|
+
gemfile.gemspec.should == "lib/gemnasium/parser.gemspec"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "parses gems of a type" do
|
90
|
+
content(%(gem "rake"))
|
91
|
+
dependency.type.should == :runtime
|
92
|
+
reset
|
93
|
+
content(%(gem "rake", :type => :development))
|
94
|
+
dependency.type.should == :development
|
95
|
+
end
|
96
|
+
|
97
|
+
it "parses gems of a group" do
|
98
|
+
content(%(gem "rake"))
|
99
|
+
dependency.groups.should == [:default]
|
100
|
+
reset
|
101
|
+
content(%(gem "rake", :group => :development))
|
102
|
+
dependency.groups.should == [:development]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "parses gems of multiple groups" do
|
106
|
+
content(%(gem "rake", :group => [:development, :test]))
|
107
|
+
dependency.groups.should == [:development, :test]
|
108
|
+
end
|
109
|
+
|
110
|
+
it "parses gems in a group" do
|
111
|
+
content(<<-EOF)
|
112
|
+
gem "rake"
|
113
|
+
group :production do
|
114
|
+
gem "pg"
|
115
|
+
end
|
116
|
+
group :development do
|
117
|
+
gem "sqlite3"
|
118
|
+
end
|
119
|
+
EOF
|
120
|
+
dependencies[0].groups.should == [:default]
|
121
|
+
dependencies[1].groups.should == [:production]
|
122
|
+
dependencies[2].groups.should == [:development]
|
123
|
+
end
|
124
|
+
|
125
|
+
it "parses gems in multiple groups" do
|
126
|
+
content(<<-EOF)
|
127
|
+
group :development, :test do
|
128
|
+
gem "sqlite3"
|
129
|
+
end
|
130
|
+
EOF
|
131
|
+
dependency.groups.should == [:development, :test]
|
132
|
+
end
|
133
|
+
|
134
|
+
it "ignores h4x" do
|
135
|
+
path = File.expand_path("../h4x.txt", __FILE__)
|
136
|
+
content(%(gem "h4x", :require => "\#{`touch #{path}`}"))
|
137
|
+
dependencies.size.should == 0
|
138
|
+
begin
|
139
|
+
File.should_not exist(path)
|
140
|
+
ensure
|
141
|
+
FileUtils.rm_f(path)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "ignores gems with a git option" do
|
146
|
+
content(%(gem "rails", :git => "https://github.com/rails/rails.git"))
|
147
|
+
dependencies.size.should == 0
|
148
|
+
end
|
149
|
+
|
150
|
+
it "ignores gems with a path option" do
|
151
|
+
content(%(gem "rails", :path => "vendor/rails"))
|
152
|
+
dependencies.size.should == 0
|
153
|
+
end
|
154
|
+
|
155
|
+
it "ignores gems in a git block" do
|
156
|
+
content(<<-EOF)
|
157
|
+
git "https://github.com/rails/rails.git" do
|
158
|
+
gem "rails"
|
159
|
+
end
|
160
|
+
EOF
|
161
|
+
dependencies.size.should == 0
|
162
|
+
end
|
163
|
+
|
164
|
+
it "ignores gems in a path block" do
|
165
|
+
content(<<-EOF)
|
166
|
+
path "vendor/rails" do
|
167
|
+
gem "rails"
|
168
|
+
end
|
169
|
+
EOF
|
170
|
+
dependencies.size.should == 0
|
171
|
+
end
|
172
|
+
|
173
|
+
it "records dependency line numbers" do
|
174
|
+
content(<<-EOF)
|
175
|
+
gem "rake"
|
176
|
+
gem "rails"
|
177
|
+
EOF
|
178
|
+
dependencies[0].instance_variable_get(:@line).should == 1
|
179
|
+
dependencies[1].instance_variable_get(:@line).should == 2
|
180
|
+
end
|
181
|
+
|
182
|
+
it "maps groups to types" do
|
183
|
+
content(<<-EOF)
|
184
|
+
gem "rake"
|
185
|
+
gem "pg", :group => :production
|
186
|
+
gem "mysql2", :group => :staging
|
187
|
+
gem "sqlite3", :group => :development
|
188
|
+
EOF
|
189
|
+
dependencies[0].type.should == :runtime
|
190
|
+
dependencies[1].type.should == :runtime
|
191
|
+
dependencies[2].type.should == :development
|
192
|
+
dependencies[3].type.should == :development
|
193
|
+
reset
|
194
|
+
Gemnasium::Parser.runtime_groups << :staging
|
195
|
+
content(<<-EOF)
|
196
|
+
gem "rake"
|
197
|
+
gem "pg", :group => :production
|
198
|
+
gem "mysql2", :group => :staging
|
199
|
+
gem "sqlite3", :group => :development
|
200
|
+
EOF
|
201
|
+
dependencies[0].type.should == :runtime
|
202
|
+
dependencies[1].type.should == :runtime
|
203
|
+
dependencies[2].type.should == :runtime
|
204
|
+
dependencies[3].type.should == :development
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Gemnasium::Parser::Gemspec do
|
4
|
+
def content(string)
|
5
|
+
@content ||= begin
|
6
|
+
indent = string.scan(/^[ \t]*(?=\S)/)
|
7
|
+
n = indent ? indent.size : 0
|
8
|
+
string.gsub(/^[ \t]{#{n}}/, "")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def gemspec
|
13
|
+
@gemspec ||= Gemnasium::Parser::Gemspec.new(@content)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dependencies
|
17
|
+
@dependencies ||= gemspec.dependencies
|
18
|
+
end
|
19
|
+
|
20
|
+
def dependency
|
21
|
+
dependencies.size.should == 1
|
22
|
+
dependencies.first
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset
|
26
|
+
@content = @gemspec = @dependencies = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "parses double quotes" do
|
30
|
+
content(<<-EOF)
|
31
|
+
Gem::Specification.new do |gem|
|
32
|
+
gem.add_dependency "rake", ">= 0.8.7"
|
33
|
+
end
|
34
|
+
EOF
|
35
|
+
dependency.name.should == "rake"
|
36
|
+
dependency.requirement.should == ">= 0.8.7"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "parses single quotes" do
|
40
|
+
content(<<-EOF)
|
41
|
+
Gem::Specification.new do |gem|
|
42
|
+
gem.add_dependency 'rake', '>= 0.8.7'
|
43
|
+
end
|
44
|
+
EOF
|
45
|
+
dependency.name.should == "rake"
|
46
|
+
dependency.requirement.should == ">= 0.8.7"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "ignores mixed quotes" do
|
50
|
+
content(<<-EOF)
|
51
|
+
Gem::Specification.new do |gem|
|
52
|
+
gem.add_dependency "rake', ">= 0.8.7"
|
53
|
+
end
|
54
|
+
EOF
|
55
|
+
dependencies.size.should == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
it "parses non-requirement gems" do
|
59
|
+
content(<<-EOF)
|
60
|
+
Gem::Specification.new do |gem|
|
61
|
+
gem.add_dependency "rake"
|
62
|
+
end
|
63
|
+
EOF
|
64
|
+
dependency.name.should == "rake"
|
65
|
+
dependency.requirement.should == ">= 0"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "parses multi-requirement gems" do
|
69
|
+
content(<<-EOF)
|
70
|
+
Gem::Specification.new do |gem|
|
71
|
+
gem.add_dependency "rake", ">= 0.8.7", "<= 0.9.2"
|
72
|
+
end
|
73
|
+
EOF
|
74
|
+
dependency.name.should == "rake"
|
75
|
+
dependency.requirement.as_list.should == ["<= 0.9.2", ">= 0.8.7"]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "parses single-element array requirement gems" do
|
79
|
+
content(<<-EOF)
|
80
|
+
Gem::Specification.new do |gem|
|
81
|
+
gem.add_dependency "rake", [">= 0.8.7"]
|
82
|
+
end
|
83
|
+
EOF
|
84
|
+
dependency.name.should == "rake"
|
85
|
+
dependency.requirement.should == ">= 0.8.7"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "parses multi-element array requirement gems" do
|
89
|
+
content(<<-EOF)
|
90
|
+
Gem::Specification.new do |gem|
|
91
|
+
gem.add_dependency "rake", [">= 0.8.7", "<= 0.9.2"]
|
92
|
+
end
|
93
|
+
EOF
|
94
|
+
dependency.name.should == "rake"
|
95
|
+
dependency.requirement.as_list.should == ["<= 0.9.2", ">= 0.8.7"]
|
96
|
+
end
|
97
|
+
|
98
|
+
it "parses runtime gems" do
|
99
|
+
content(<<-EOF)
|
100
|
+
Gem::Specification.new do |gem|
|
101
|
+
gem.add_dependency "rake"
|
102
|
+
gem.add_runtime_dependency "rails"
|
103
|
+
end
|
104
|
+
EOF
|
105
|
+
dependencies[0].type.should == :runtime
|
106
|
+
dependencies[1].type.should == :runtime
|
107
|
+
end
|
108
|
+
|
109
|
+
it "parses dependency gems" do
|
110
|
+
content(<<-EOF)
|
111
|
+
Gem::Specification.new do |gem|
|
112
|
+
gem.add_development_dependency "rake"
|
113
|
+
end
|
114
|
+
EOF
|
115
|
+
dependency.type.should == :development
|
116
|
+
end
|
117
|
+
|
118
|
+
it "records dependency line numbers" do
|
119
|
+
content(<<-EOF)
|
120
|
+
Gem::Specification.new do |gem|
|
121
|
+
gem.add_dependency "rake"
|
122
|
+
gem.add_dependency "rails"
|
123
|
+
end
|
124
|
+
EOF
|
125
|
+
dependencies[0].instance_variable_get(:@line).should == 2
|
126
|
+
dependencies[1].instance_variable_get(:@line).should == 3
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Gemnasium::Parser do
|
4
|
+
describe ".gemfile" do
|
5
|
+
it "requires a single string argument" do
|
6
|
+
expect{ Gemnasium::Parser.gemfile }.to raise_error(ArgumentError)
|
7
|
+
expect{ Gemnasium::Parser.gemfile("") }.to_not raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns a Gemfile" do
|
11
|
+
Gemnasium::Parser.gemfile("").should be_a(Gemnasium::Parser::Gemfile)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".gemspec" do
|
16
|
+
it "requires a single string argument" do
|
17
|
+
expect{ Gemnasium::Parser.gemspec }.to raise_error(ArgumentError)
|
18
|
+
expect{ Gemnasium::Parser.gemspec("") }.to_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns a Gemspec" do
|
22
|
+
Gemnasium::Parser.gemspec("").should be_a(Gemnasium::Parser::Gemspec)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "gemnasium/parser"
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gemnasium-parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Steve Richert
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-15 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70223212269420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.7
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70223212269420
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70223212268920 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.7'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70223212268920
|
36
|
+
description: Safely parse Gemfiles and gemspecs
|
37
|
+
email: steve.richert@gmail.com
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- .gitignore
|
43
|
+
- .travis.yml
|
44
|
+
- Gemfile
|
45
|
+
- LICENSE
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- gemnasium-parser.gemspec
|
49
|
+
- lib/gemnasium/parser.rb
|
50
|
+
- lib/gemnasium/parser/configuration.rb
|
51
|
+
- lib/gemnasium/parser/gemfile.rb
|
52
|
+
- lib/gemnasium/parser/gemspec.rb
|
53
|
+
- lib/gemnasium/parser/patterns.rb
|
54
|
+
- lib/gemnasium/parser/version.rb
|
55
|
+
- spec/gemnasium/parser/gemfile_spec.rb
|
56
|
+
- spec/gemnasium/parser/gemspec_spec.rb
|
57
|
+
- spec/gemnasium/parser_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
homepage: https://github.com/laserlemon/gemnasium-parser
|
60
|
+
licenses: []
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.8.12
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Safely parse Gemfiles and gemspecs
|
83
|
+
test_files:
|
84
|
+
- spec/gemnasium/parser/gemfile_spec.rb
|
85
|
+
- spec/gemnasium/parser/gemspec_spec.rb
|
86
|
+
- spec/gemnasium/parser_spec.rb
|
87
|
+
- spec/spec_helper.rb
|