c66-gemnasium-parser 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bff190f9fe0cc29b2412cf312c747cfe7648df8a9b1ca3d42b0704fe593a816f
4
+ data.tar.gz: 6c4636227634775f256cbf1468d0762c0304d54d4e00354274bb55c5da05ed81
5
+ SHA512:
6
+ metadata.gz: 66a7fee11bbb24d29c36fe06b3115d843b13b2f31b3f82c3b730873a613ef1e5e546eead78a5590abf1d0c5a5f90544f0d3877458f9daf92ffc07ecdb88c9cbe
7
+ data.tar.gz: 0a6cc3b8581ad2b23ee5b37d5b07eb738b0bfda26479c01b8fecdf29beee79e52568e227c5e6117d6d6767bd21373403277efe5670e321284b45662f191784b9
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rvmrc
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - 2.0.0
@@ -0,0 +1,19 @@
1
+ ## 0.1.9 / 2012-11-19
2
+
3
+ Bugfixes:
4
+
5
+ * remove CR chars from content before parsing since it breaks patterns matching
6
+
7
+ ## 0.1.8 / 2012-11-18
8
+
9
+ Bugfixes:
10
+
11
+ * support :github option and exclude gems that use it
12
+ * add CHANGELOG
13
+
14
+ ## 0.1.7 2012-10-17
15
+
16
+ Bugfixes:
17
+
18
+ * fix pattern to allow gem's name with a period
19
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "coveralls", "~> 0.6", require: false
6
+ gem "rake", ">= 0.8.7"
7
+ gem "rspec", "~> 2.4"
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.
@@ -0,0 +1,53 @@
1
+ # Gemnasium::Parser
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/formatador.svg)]()
4
+ [![Build Status](https://img.shields.io/travis/gemnasium/gemnasium-parser/master.svg)](https://travis-ci.org/gemnasium/gemnasium-parser)
5
+ [![Dependency Status](https://img.shields.io/gemnasium/gemnasium/gemnasium-parser.svg)](https://gemnasium.com/gemnasium/gemnasium-parser)
6
+
7
+ The [Gemnasium](https://gemnasium.com/) parser determines gem dependencies from gemfiles and gemspecs, without evaluating the Ruby.
8
+
9
+ ## Why?
10
+
11
+ [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!
12
+
13
+ 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.
14
+
15
+ ### Solution #1
16
+
17
+ 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.
18
+
19
+ 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.
20
+
21
+ ### Solution #2
22
+
23
+ Parse Ruby like Ruby parses Ruby.
24
+
25
+ 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.
26
+
27
+ 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.
28
+
29
+ ### Third try’s the charm
30
+
31
+ 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?
32
+
33
+ Regular expressions!
34
+
35
+ 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.
36
+
37
+ For a more comprehensive list of its abilities, see the [specs](https://github.com/laserlemon/gemnasium-parser/tree/master/spec).
38
+
39
+ ## Contributing <a name="contributing"></a>
40
+
41
+ 1. Fork it
42
+ 2. Create your branch (`git checkout -b my-bugfix`)
43
+ 3. Commit your changes (`git commit -am "Fix my bug"`)
44
+ 4. Push your branch (`git push origin my-bugfix`)
45
+ 5. Send a pull request
46
+
47
+ ## Problems?
48
+
49
+ If you have a gemfile or gemspec that the Gemnasium parser screws up…
50
+
51
+ 1. Boil it down to its simplest problematic form
52
+ 2. Write a failing spec
53
+ 3. See [Contributing](#contributing)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :test => :spec
9
+ task :default => :spec
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "c66-gemnasium-parser"
5
+ gem.version = "0.1.10"
6
+
7
+ gem.authors = "Steve Richert"
8
+ gem.email = "steve.richert@gmail.com"
9
+ gem.description = "Safely parse Gemfiles and gemspecs"
10
+ gem.summary = gem.description
11
+ gem.homepage = "https://github.com/cloud66-oss/gemnasium-parser"
12
+
13
+ gem.add_development_dependency "bundler", "~> 1.0"
14
+
15
+ gem.files = `git ls-files`.split($\)
16
+ gem.test_files = gem.files.grep(/^spec\//)
17
+ gem.require_paths = ["lib"]
18
+ end
@@ -0,0 +1,21 @@
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
+ # Remove CR chars "\r" from content since it breaks Patterns matching
11
+ # TODO: Find something cleaner than this workaround
12
+ Gemnasium::Parser::Gemfile.new(content.gsub("\r",''))
13
+ end
14
+
15
+ def self.gemspec(content)
16
+ # Remove CR chars "\r" from content since it breaks Patterns matching
17
+ # TODO: Find something cleaner than this workaround
18
+ Gemnasium::Parser::Gemspec.new(content.gsub("\r",''))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Gemnasium
2
+ module Parser
3
+ module Configuration
4
+ attr_writer :runtime_groups
5
+
6
+ def runtime_groups
7
+ @runtime_groups ||= [:default, :production]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,122 @@
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 ruby
23
+ if @ruby = content.match(Patterns::RUBY_CALL)
24
+ return @ruby[:version]
25
+ else
26
+ return nil
27
+ end
28
+ end
29
+
30
+ def gemspec
31
+ @gemspec = if gemspec_match
32
+ opts = Patterns.options(gemspec_match["opts"])
33
+ path = opts["path"]
34
+ name = opts["name"] || "*"
35
+ File.join(*[path, "#{name}.gemspec"].compact)
36
+ end
37
+ end
38
+
39
+ def gemspec?
40
+ !!gemspec
41
+ end
42
+
43
+ private
44
+ def gem_matches
45
+ @gem_matches ||= matches(Patterns::GEM_CALL)
46
+ end
47
+
48
+ def matches(pattern)
49
+ [].tap{|m| content.scan(pattern){ m << Regexp.last_match } }
50
+ end
51
+
52
+ def dependency(match)
53
+ opts = Patterns.options(match["opts"])
54
+ return nil if exclude?(match, opts)
55
+ clean!(match, opts)
56
+ name, reqs = match["name"], [match["req1"], match["req2"]].compact
57
+ Bundler::Dependency.new(name, reqs, opts).tap do |dep|
58
+ line = content.slice(0, match.begin(0)).count("\n") + 1
59
+ dep.instance_variable_set(:@line, line)
60
+ end
61
+ end
62
+
63
+ def groups(match)
64
+ group = group_matches.detect{|m| in_block?(match, m) }
65
+ group && Patterns.values(group[:grps])
66
+ end
67
+
68
+ def in_block?(inner, outer)
69
+ outer.begin(:blk) <= inner.begin(0) && outer.end(:blk) >= inner.end(0)
70
+ end
71
+
72
+ def group_matches
73
+ @group_matches ||= matches(Patterns::GROUP_CALL)
74
+ end
75
+
76
+ def exclude?(match, opts)
77
+ git?(match, opts) || github?(match, opts) || path?(match, opts)
78
+ end
79
+
80
+ def git?(match, opts)
81
+ opts["git"] || in_git_block?(match)
82
+ end
83
+
84
+ def github?(match, opts)
85
+ opts["github"]
86
+ end
87
+
88
+ def in_git_block?(match)
89
+ git_matches.any?{|m| in_block?(match, m) }
90
+ end
91
+
92
+ def git_matches
93
+ @git_matches ||= matches(Patterns::GIT_CALL)
94
+ end
95
+
96
+ def path?(match, opts)
97
+ opts["path"] || in_path_block?(match)
98
+ end
99
+
100
+ def in_path_block?(match)
101
+ path_matches.any?{|m| in_block?(match, m) }
102
+ end
103
+
104
+ def path_matches
105
+ @path_matches ||= matches(Patterns::PATH_CALL)
106
+ end
107
+
108
+ def clean!(match, opts)
109
+ opts["group"] ||= opts.delete("groups")
110
+ opts["group"] ||= groups(match)
111
+ groups = Array(opts["group"]).flatten.compact
112
+ runtime = groups.empty? || !(groups & Parser.runtime_groups).empty?
113
+ opts["type"] ||= runtime ? :runtime : :development
114
+ end
115
+
116
+ def gemspec_match
117
+ return @gemspec_match if defined?(@gemspec_match)
118
+ @gemspec_match = content.match(Patterns::GEMSPEC_CALL)
119
+ end
120
+ end
121
+ end
122
+ 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::ADD_DEPENDENCY_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,66 @@
1
+ module Gemnasium
2
+ module Parser
3
+ module Patterns
4
+ GEM_NAME = /[a-zA-Z0-9\-_\.]+/
5
+ QUOTED_GEM_NAME = /(?:(?<gq>["'])(?<name>#{GEM_NAME})\k<gq>|%q<(?<name>#{GEM_NAME})>)/
6
+
7
+ MATCHER = /(?:=|!=|>|<|>=|<=|~>)/
8
+ VERSION = /[0-9]+(?:\.[a-zA-Z0-9]+)*/
9
+ REQUIREMENT = /[ \t]*(?:#{MATCHER}[ \t]*)?#{VERSION}[ \t]*/
10
+ REQUIREMENT_LIST = /(?<qr1>["'])(?<req1>#{REQUIREMENT})\k<qr1>(?:[ \t]*,[ \t]*(?<qr2>["'])(?<req2>#{REQUIREMENT})\k<qr2>)?/
11
+ REQUIREMENTS = /(?:#{REQUIREMENT_LIST}|\[[ \t]*#{REQUIREMENT_LIST}[ \t]*\])/
12
+
13
+ KEY = /(?::\w+|:?"\w+"|:?'\w+')/
14
+ SYMBOL = /(?::\w+|:"[^"#]+"|:'[^']+')/
15
+ STRING = /(?:"[^"#]*"|'[^']*')/
16
+ BOOLEAN = /(?:true|false)/
17
+ NIL = /nil/
18
+ ELEMENT = /(?:#{SYMBOL}|#{STRING})/
19
+ ARRAY = /\[(?:#{ELEMENT}(?:[ \t]*,[ \t]*#{ELEMENT})*)?\]/
20
+ VALUE = /(?:#{BOOLEAN}|#{NIL}|#{ELEMENT}|#{ARRAY}|)/
21
+ PAIR = /(?:(#{KEY})[ \t]*=>[ \t]*(#{VALUE})|(\w+):[ \t]+(#{VALUE}))/
22
+ OPTIONS = /#{PAIR}(?:[ \t]*,[ \t]*#{PAIR})*/
23
+ COMMENT = /(#[^\n]*)?/
24
+
25
+ GEM_CALL = /^[ \t]*gem\(?[ \t]*#{QUOTED_GEM_NAME}(?:[ \t]*,[ \t]*#{REQUIREMENT_LIST})?(?:[ \t]*,[ \t]*(?<opts>#{OPTIONS}))?[ \t]*\)?[ \t]*#{COMMENT}$/
26
+ RUBY_CALL = /^[ \t]*ruby\(?[ \t]*(?:(?<gq>["'])(?<version>#{VERSION})\k<gq>)?[ \t]*#{COMMENT}$/
27
+
28
+ SYMBOLS = /#{SYMBOL}([ \t]*,[ \t]*#{SYMBOL})*/
29
+ GROUP_CALL = /^(?<i1>[ \t]*)group\(?[ \t]*(?<grps>#{SYMBOLS})[ \t]*\)?[ \t]+do[ \t]*?\n(?<blk>.*?)\n^\k<i1>end[ \t]*$/m
30
+
31
+ GIT_CALL = /^(?<i1>[ \t]*)git[ \(][^\n]*?do[ \t]*?\n(?<blk>.*?)\n^\k<i1>end[ \t]*$/m
32
+
33
+ PATH_CALL = /^(?<i1>[ \t]*)path[ \(][^\n]*?do[ \t]*?\n(?<blk>.*?)\n^\k<i1>end[ \t]*$/m
34
+
35
+ GEMSPEC_CALL = /^[ \t]*gemspec(?:\(?[ \t]*(?<opts>#{OPTIONS}))?[ \t]*\)?[ \t]*$/
36
+
37
+ ADD_DEPENDENCY_CALL = /^[ \t]*\w+\.add(?<type>_runtime|_development)?_dependency\(?[ \t]*#{QUOTED_GEM_NAME}(?:[ \t]*,[ \t]*#{REQUIREMENTS})?[ \t]*\)?[ \t]*#{COMMENT}$/
38
+
39
+ def self.options(string)
40
+ {}.tap do |hash|
41
+ return hash unless string
42
+ pairs = Hash[*string.match(OPTIONS).captures.compact]
43
+ pairs.each{|k,v| hash[key(k)] = value(v) }
44
+ end
45
+ end
46
+
47
+ def self.key(string)
48
+ string.tr(%(:"'), "")
49
+ end
50
+
51
+ def self.value(string)
52
+ case string
53
+ when ARRAY then values(string.tr("[]", ""))
54
+ when SYMBOL then string.tr(%(:"'), "").to_sym
55
+ when STRING then string.tr(%("'), "")
56
+ when BOOLEAN then string == "true"
57
+ when NIL then nil
58
+ end
59
+ end
60
+
61
+ def self.values(string)
62
+ string.strip.split(/[ \t]*,[ \t]*/).map{|v| value(v) }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,294 @@
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.as_list.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.as_list.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 gems with a period in the name" do
47
+ content(%(gem "pygment.rb", ">= 0.8.7"))
48
+ dependency.name.should == "pygment.rb"
49
+ dependency.requirement.as_list.should == [">= 0.8.7"]
50
+ end
51
+
52
+ it "parses non-requirement gems" do
53
+ content(%(gem "rake"))
54
+ dependency.name.should == "rake"
55
+ dependency.requirement.as_list.should == [">= 0"]
56
+ end
57
+
58
+ it "parses multi-requirement gems" do
59
+ content(%(gem "rake", ">= 0.8.7", "<= 0.9.2"))
60
+ dependency.name.should == "rake"
61
+ dependency.requirement.as_list.should == ["<= 0.9.2", ">= 0.8.7"]
62
+ end
63
+
64
+ it "parses gems with options" do
65
+ content(%(gem "rake", ">= 0.8.7", :require => false))
66
+ dependency.name.should == "rake"
67
+ dependency.requirement.as_list.should == [">= 0.8.7"]
68
+ end
69
+
70
+ it "listens for gemspecs" do
71
+ content(%(gemspec))
72
+ gemfile.should be_gemspec
73
+ gemfile.gemspec.should == "*.gemspec"
74
+ reset
75
+ content(%(gem "rake"))
76
+ gemfile.should_not be_gemspec
77
+ gemfile.gemspec.should be_nil
78
+ end
79
+
80
+ it "parses gemspecs with a name option" do
81
+ content(%(gemspec :name => "c66-gemnasium-parser"))
82
+ gemfile.gemspec.should == "gemnasium-parser.gemspec"
83
+ end
84
+
85
+ it "parses gemspecs with a path option" do
86
+ content(%(gemspec :path => "lib/gemnasium"))
87
+ gemfile.gemspec.should == "lib/gemnasium/*.gemspec"
88
+ end
89
+
90
+ it "parses gemspecs with name and path options" do
91
+ content(%(gemspec :name => "parser", :path => "lib/gemnasium"))
92
+ gemfile.gemspec.should == "lib/gemnasium/parser.gemspec"
93
+ end
94
+
95
+ it "parses gemspecs with parentheses" do
96
+ content(%(gemspec(:name => "gemnasium-parser")))
97
+ gemfile.should be_gemspec
98
+ end
99
+
100
+ it "parses gems of a type" do
101
+ content(%(gem "rake"))
102
+ dependency.type.should == :runtime
103
+ reset
104
+ content(%(gem "rake", :type => :development))
105
+ dependency.type.should == :development
106
+ end
107
+
108
+ it "parses gems of a group" do
109
+ content(%(gem "rake"))
110
+ dependency.groups.should == [:default]
111
+ reset
112
+ content(%(gem "rake", :group => :development))
113
+ dependency.groups.should == [:development]
114
+ end
115
+
116
+ it "parses gems of multiple groups" do
117
+ content(%(gem "rake", :group => [:development, :test]))
118
+ dependency.groups.should == [:development, :test]
119
+ end
120
+
121
+ it "recognizes :groups" do
122
+ content(%(gem "rake", :groups => [:development, :test]))
123
+ dependency.groups.should == [:development, :test]
124
+ end
125
+
126
+ it "parses gems in a group" do
127
+ content(<<-EOF)
128
+ gem "rake"
129
+ group :production do
130
+ gem "pg"
131
+ end
132
+ group :development do
133
+ gem "sqlite3"
134
+ end
135
+ EOF
136
+ dependencies[0].groups.should == [:default]
137
+ dependencies[1].groups.should == [:production]
138
+ dependencies[2].groups.should == [:development]
139
+ end
140
+
141
+ it "parses gems in a group with parentheses" do
142
+ content(<<-EOF)
143
+ group(:production) do
144
+ gem "pg"
145
+ end
146
+ EOF
147
+ dependency.groups.should == [:production]
148
+ end
149
+
150
+ it "parses gems in multiple groups" do
151
+ content(<<-EOF)
152
+ group :development, :test do
153
+ gem "sqlite3"
154
+ end
155
+ EOF
156
+ dependency.groups.should == [:development, :test]
157
+ end
158
+
159
+ it "parses multiple gems in a group" do
160
+ content(<<-EOF)
161
+ group :development do
162
+ gem "rake"
163
+ gem "sqlite3"
164
+ end
165
+ EOF
166
+ dependencies[0].groups.should == [:development]
167
+ dependencies[1].groups.should == [:development]
168
+ end
169
+
170
+ it "parses multiple gems in multiple groups" do
171
+ content(<<-EOF)
172
+ group :development, :test do
173
+ gem "rake"
174
+ gem "sqlite3"
175
+ end
176
+ EOF
177
+ dependencies[0].groups.should == [:development, :test]
178
+ dependencies[1].groups.should == [:development, :test]
179
+ end
180
+
181
+ it "ignores h4x" do
182
+ path = File.expand_path("../h4x.txt", __FILE__)
183
+ content(%(gem "h4x", :require => "\#{`touch #{path}`}"))
184
+ dependencies.size.should == 0
185
+ begin
186
+ File.should_not exist(path)
187
+ ensure
188
+ FileUtils.rm_f(path)
189
+ end
190
+ end
191
+
192
+ it "ignores gems with a git option" do
193
+ content(%(gem "rails", :git => "https://github.com/rails/rails.git"))
194
+ dependencies.size.should == 0
195
+ end
196
+
197
+ it "ignores gems with a github option" do
198
+ content(%(gem "rails", :github => "rails/rails"))
199
+ dependencies.size.should == 0
200
+ end
201
+
202
+ it "ignores gems with a path option" do
203
+ content(%(gem "rails", :path => "vendor/rails"))
204
+ dependencies.size.should == 0
205
+ end
206
+
207
+ it "ignores gems in a git block" do
208
+ content(<<-EOF)
209
+ git "https://github.com/rails/rails.git" do
210
+ gem "rails"
211
+ end
212
+ EOF
213
+ dependencies.size.should == 0
214
+ end
215
+
216
+ it "ignores gems in a git block with parentheses" do
217
+ content(<<-EOF)
218
+ git("https://github.com/rails/rails.git") do
219
+ gem "rails"
220
+ end
221
+ EOF
222
+ dependencies.size.should == 0
223
+ end
224
+
225
+ it "ignores gems in a path block" do
226
+ content(<<-EOF)
227
+ path "vendor/rails" do
228
+ gem "rails"
229
+ end
230
+ EOF
231
+ dependencies.size.should == 0
232
+ end
233
+
234
+ it "ignores gems in a path block with parentheses" do
235
+ content(<<-EOF)
236
+ path("vendor/rails") do
237
+ gem "rails"
238
+ end
239
+ EOF
240
+ dependencies.size.should == 0
241
+ end
242
+
243
+ it "records dependency line numbers" do
244
+ content(<<-EOF)
245
+ gem "rake"
246
+
247
+ gem "rails"
248
+ EOF
249
+ dependencies[0].instance_variable_get(:@line).should == 1
250
+ dependencies[1].instance_variable_get(:@line).should == 3
251
+ end
252
+
253
+ it "maps groups to types" do
254
+ content(<<-EOF)
255
+ gem "rake"
256
+ gem "pg", :group => :production
257
+ gem "mysql2", :group => :staging
258
+ gem "sqlite3", :group => :development
259
+ EOF
260
+ dependencies[0].type.should == :runtime
261
+ dependencies[1].type.should == :runtime
262
+ dependencies[2].type.should == :development
263
+ dependencies[3].type.should == :development
264
+ reset
265
+ Gemnasium::Parser.runtime_groups << :staging
266
+ content(<<-EOF)
267
+ gem "rake"
268
+ gem "pg", :group => :production
269
+ gem "mysql2", :group => :staging
270
+ gem "sqlite3", :group => :development
271
+ EOF
272
+ dependencies[0].type.should == :runtime
273
+ dependencies[1].type.should == :runtime
274
+ dependencies[2].type.should == :runtime
275
+ dependencies[3].type.should == :development
276
+ end
277
+
278
+ it "parses parentheses" do
279
+ content(%(gem("rake", ">= 0.8.7")))
280
+ dependency.name.should == "rake"
281
+ dependency.requirement.as_list.should == [">= 0.8.7"]
282
+ end
283
+
284
+ it "parses gems followed by inline comments" do
285
+ content(%(gem "rake", ">= 0.8.7" # Comment))
286
+ dependency.name.should == "rake"
287
+ dependency.requirement.as_list.should == [">= 0.8.7"]
288
+ end
289
+
290
+ it "parses oddly quoted gems" do
291
+ content(%(gem %q<rake>))
292
+ dependency.name.should == "rake"
293
+ end
294
+ end
@@ -0,0 +1,159 @@
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.as_list.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.as_list.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 gems with a period in the name" do
59
+ content(<<-EOF)
60
+ Gem::Specification.new do |gem|
61
+ gem.add_dependency "pygment.rb", ">= 0.8.7"
62
+ end
63
+ EOF
64
+ dependency.name.should == "pygment.rb"
65
+ dependency.requirement.as_list.should == [">= 0.8.7"]
66
+ end
67
+
68
+ it "parses non-requirement gems" do
69
+ content(<<-EOF)
70
+ Gem::Specification.new do |gem|
71
+ gem.add_dependency "rake"
72
+ end
73
+ EOF
74
+ dependency.name.should == "rake"
75
+ dependency.requirement.as_list.should == [">= 0"]
76
+ end
77
+
78
+ it "parses multi-requirement gems" do
79
+ content(<<-EOF)
80
+ Gem::Specification.new do |gem|
81
+ gem.add_dependency "rake", ">= 0.8.7", "<= 0.9.2"
82
+ end
83
+ EOF
84
+ dependency.name.should == "rake"
85
+ dependency.requirement.as_list.should == ["<= 0.9.2", ">= 0.8.7"]
86
+ end
87
+
88
+ it "parses single-element array requirement gems" do
89
+ content(<<-EOF)
90
+ Gem::Specification.new do |gem|
91
+ gem.add_dependency "rake", [">= 0.8.7"]
92
+ end
93
+ EOF
94
+ dependency.name.should == "rake"
95
+ dependency.requirement.as_list.should == [">= 0.8.7"]
96
+ end
97
+
98
+ it "parses multi-element array requirement gems" do
99
+ content(<<-EOF)
100
+ Gem::Specification.new do |gem|
101
+ gem.add_dependency "rake", [">= 0.8.7", "<= 0.9.2"]
102
+ end
103
+ EOF
104
+ dependency.name.should == "rake"
105
+ dependency.requirement.as_list.should == ["<= 0.9.2", ">= 0.8.7"]
106
+ end
107
+
108
+ it "parses runtime gems" do
109
+ content(<<-EOF)
110
+ Gem::Specification.new do |gem|
111
+ gem.add_dependency "rake"
112
+ gem.add_runtime_dependency "rails"
113
+ end
114
+ EOF
115
+ dependencies[0].type.should == :runtime
116
+ dependencies[1].type.should == :runtime
117
+ end
118
+
119
+ it "parses dependency gems" do
120
+ content(<<-EOF)
121
+ Gem::Specification.new do |gem|
122
+ gem.add_development_dependency "rake"
123
+ end
124
+ EOF
125
+ dependency.type.should == :development
126
+ end
127
+
128
+ it "records dependency line numbers" do
129
+ content(<<-EOF)
130
+ Gem::Specification.new do |gem|
131
+ gem.add_dependency "rake"
132
+
133
+ gem.add_dependency "rails"
134
+ end
135
+ EOF
136
+ dependencies[0].instance_variable_get(:@line).should == 2
137
+ dependencies[1].instance_variable_get(:@line).should == 4
138
+ end
139
+
140
+ it "parses parentheses" do
141
+ content(<<-EOF)
142
+ Gem::Specification.new do |gem|
143
+ gem.add_dependency("rake", ">= 0.8.7")
144
+ end
145
+ EOF
146
+ dependency.name.should == "rake"
147
+ dependency.requirement.as_list.should == [">= 0.8.7"]
148
+ end
149
+
150
+ it "parses gems followed by inline comments" do
151
+ content(<<-EOF)
152
+ Gem::Specification.new do |gem|
153
+ gem.add_dependency "rake", ">= 0.8.7" # Comment
154
+ end
155
+ EOF
156
+ dependency.name.should == "rake"
157
+ dependency.requirement.as_list.should == [">= 0.8.7"]
158
+ end
159
+ end
@@ -0,0 +1,34 @@
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
+
14
+ it "removes CR chars from content" do
15
+ Gemnasium::Parser.gemfile("\r").content.match("\r").should be_nil
16
+ end
17
+
18
+ end
19
+
20
+ describe ".gemspec" do
21
+ it "requires a single string argument" do
22
+ expect{ Gemnasium::Parser.gemspec }.to raise_error(ArgumentError)
23
+ expect{ Gemnasium::Parser.gemspec("") }.to_not raise_error
24
+ end
25
+
26
+ it "returns a Gemspec" do
27
+ Gemnasium::Parser.gemspec("").should be_a(Gemnasium::Parser::Gemspec)
28
+ end
29
+
30
+ it "removes CR chars from content" do
31
+ Gemnasium::Parser.gemspec("\r").content.match("\r").should be_nil
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ require "coveralls"
2
+ Coveralls.wear!
3
+
4
+ require "gemnasium/parser"
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: c66-gemnasium-parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.10
5
+ platform: ruby
6
+ authors:
7
+ - Steve Richert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ description: Safely parse Gemfiles and gemspecs
28
+ email: steve.richert@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".gitignore"
34
+ - ".travis.yml"
35
+ - CHANGELOG.md
36
+ - Gemfile
37
+ - LICENSE
38
+ - README.md
39
+ - Rakefile
40
+ - gemnasium-parser.gemspec
41
+ - lib/gemnasium/parser.rb
42
+ - lib/gemnasium/parser/configuration.rb
43
+ - lib/gemnasium/parser/gemfile.rb
44
+ - lib/gemnasium/parser/gemspec.rb
45
+ - lib/gemnasium/parser/patterns.rb
46
+ - spec/gemnasium/parser/gemfile_spec.rb
47
+ - spec/gemnasium/parser/gemspec_spec.rb
48
+ - spec/gemnasium/parser_spec.rb
49
+ - spec/spec_helper.rb
50
+ homepage: https://github.com/cloud66-oss/gemnasium-parser
51
+ licenses: []
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.7.7
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Safely parse Gemfiles and gemspecs
73
+ test_files:
74
+ - spec/gemnasium/parser/gemfile_spec.rb
75
+ - spec/gemnasium/parser/gemspec_spec.rb
76
+ - spec/gemnasium/parser_spec.rb
77
+ - spec/spec_helper.rb