librariesio-gem-parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee10e89cac8d75e23956264a511b98302f723179
4
+ data.tar.gz: 6cf5b395df0bab001f382b2bf3d2604eb0e768c7
5
+ SHA512:
6
+ metadata.gz: 934211a80747662da08b4d997facdcf7086351297ae527b0466a62977708db039238d9bc1d63c5bf87eb0d74d80311a055e40e9c4dec92409192f1414cfbc5ac
7
+ data.tar.gz: ee17f4dc806bc5e49b2c791f4a5c145c6703fee288b7be5c53a93c00c90565e3f7a7c67a113f77ff31f52d7df818c147a4bd348f0186ebc0b02efd6e03334365
@@ -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
19
+ with a period
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
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,55 @@
1
+ # Gemnasium::Parser
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/gemnasium-parser.png)](http://badge.fury.io/rb/gemnasium-parser)
4
+ [![Build Status](https://travis-ci.org/gemnasium/gemnasium-parser.png?branch=master)](https://travis-ci.org/gemnasium/gemnasium-parser)
5
+ [![Dependency Status](https://gemnasium.com/laserlemon/gemnasium-parser.png)](https://gemnasium.com/laserlemon/gemnasium-parser)
6
+ [![Code Climate](https://codeclimate.com/github/laserlemon/gemnasium-parser.png)](https://codeclimate.com/github/laserlemon/gemnasium-parser)
7
+ [![Coverage Status](https://coveralls.io/repos/laserlemon/gemnasium-parser/badge.png?branch=master)](https://coveralls.io/r/laserlemon/gemnasium-parser)
8
+
9
+ The [Gemnasium](https://gemnasium.com/) parser determines gem dependencies from gemfiles and gemspecs, without evaluating the Ruby.
10
+
11
+ ## Why?
12
+
13
+ [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!
14
+
15
+ 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.
16
+
17
+ ### Solution #1
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ ### Solution #2
24
+
25
+ Parse Ruby like Ruby parses Ruby.
26
+
27
+ 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.
28
+
29
+ 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.
30
+
31
+ ### Third try’s the charm
32
+
33
+ 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?
34
+
35
+ Regular expressions!
36
+
37
+ 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.
38
+
39
+ For a more comprehensive list of its abilities, see the [specs](https://github.com/laserlemon/gemnasium-parser/tree/master/spec).
40
+
41
+ ## Contributing <a name="contributing"></a>
42
+
43
+ 1. Fork it
44
+ 2. Create your branch (`git checkout -b my-bugfix`)
45
+ 3. Commit your changes (`git commit -am "Fix my bug"`)
46
+ 4. Push your branch (`git push origin my-bugfix`)
47
+ 5. Send a pull request
48
+
49
+ ## Problems?
50
+
51
+ If you have a gemfile or gemspec that the Gemnasium parser screws up…
52
+
53
+ 1. Boil it down to its simplest problematic form
54
+ 2. Write a failing spec
55
+ 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,35 @@
1
+ require "gemnasium/parser/configuration"
2
+ require "gemnasium/parser/gemfile"
3
+ require "gemnasium/parser/podfile"
4
+ require "gemnasium/parser/gemspec"
5
+ require "gemnasium/parser/podspec"
6
+
7
+ module Gemnasium
8
+ module Parser
9
+ extend Configuration
10
+
11
+ def self.gemfile(content)
12
+ # Remove CR chars "\r" from content since it breaks Patterns matching
13
+ # TODO: Find something cleaner than this workaround
14
+ Gemnasium::Parser::Gemfile.new(content.gsub("\r",''))
15
+ end
16
+
17
+ def self.podfile(content)
18
+ # Remove CR chars "\r" from content since it breaks Patterns matching
19
+ # TODO: Find something cleaner than this workaround
20
+ Gemnasium::Parser::Podfile.new(content.gsub("\r",''))
21
+ end
22
+
23
+ def self.gemspec(content)
24
+ # Remove CR chars "\r" from content since it breaks Patterns matching
25
+ # TODO: Find something cleaner than this workaround
26
+ Gemnasium::Parser::Gemspec.new(content.gsub("\r",''))
27
+ end
28
+
29
+ def self.podspec(content)
30
+ # Remove CR chars "\r" from content since it breaks Patterns matching
31
+ # TODO: Find something cleaner than this workaround
32
+ Gemnasium::Parser::Podspec.new(content.gsub("\r",''))
33
+ end
34
+ end
35
+ 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,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
+ clean!(match, opts)
47
+ name, reqs = match["name"], [match["req1"], match["req2"]].compact
48
+ Bundler::Dependency.new(name, reqs, opts).tap do |dep|
49
+ line = content.slice(0, match.begin(0)).count("\n") + 1
50
+ dep.instance_variable_set(:@line, line)
51
+ end
52
+ end
53
+
54
+ def groups(match)
55
+ group = group_matches.detect{|m| in_block?(match, m) }
56
+ group && Patterns.values(group[:grps])
57
+ end
58
+
59
+ def in_block?(inner, outer)
60
+ outer.begin(:blk) <= inner.begin(0) && outer.end(:blk) >= inner.end(0)
61
+ end
62
+
63
+ def group_matches
64
+ @group_matches ||= matches(Patterns::GROUP_CALL)
65
+ end
66
+
67
+ def git?(match, opts)
68
+ opts["git"] || in_git_block?(match)
69
+ end
70
+
71
+ def github?(match, opts)
72
+ opts["github"]
73
+ end
74
+
75
+ def in_git_block?(match)
76
+ git_matches.any?{|m| in_block?(match, m) }
77
+ end
78
+
79
+ def git_matches
80
+ @git_matches ||= matches(Patterns::GIT_CALL)
81
+ end
82
+
83
+ def path?(match, opts)
84
+ opts["path"] || in_path_block?(match)
85
+ end
86
+
87
+ def in_path_block?(match)
88
+ path_matches.any?{|m| in_block?(match, m) }
89
+ end
90
+
91
+ def path_matches
92
+ @path_matches ||= matches(Patterns::PATH_CALL)
93
+ end
94
+
95
+ def clean!(match, opts)
96
+ opts["group"] ||= opts.delete("groups")
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::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,69 @@
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
+ QUOTED_POD_NAME = /(?:(?<gq>["'])(?<name>#{GEM_NAME})(\/[a-zA-Z0-9\-_\.]+)?\k<gq>|%q<(?<name>#{GEM_NAME})(\/[a-zA-Z0-9\-_\.]+)?>)/
8
+
9
+ MATCHER = /(?:=|!=|>|<|>=|<=|~>)/
10
+ VERSION = /[0-9]+(?:\.[a-zA-Z0-9]+)*/
11
+ REQUIREMENT = /[ \t]*(?:#{MATCHER}[ \t]*)?#{VERSION}[ \t]*/
12
+ REQUIREMENT_LIST = /(?<qr1>["'])(?<req1>#{REQUIREMENT})\k<qr1>(?:[ \t]*,[ \t]*(?<qr2>["'])(?<req2>#{REQUIREMENT})\k<qr2>)?/
13
+ REQUIREMENTS = /(?:#{REQUIREMENT_LIST}|\[[ \t]*#{REQUIREMENT_LIST}[ \t]*\])/
14
+
15
+ KEY = /(?::\w+|:?"\w+"|:?'\w+')/
16
+ SYMBOL = /(?::\w+|:"[^"#]+"|:'[^']+')/
17
+ STRING = /(?:"[^"#]*"|'[^']*')/
18
+ BOOLEAN = /(?:true|false)/
19
+ NIL = /nil/
20
+ ELEMENT = /(?:#{SYMBOL}|#{STRING})/
21
+ ARRAY = /\[(?:#{ELEMENT}(?:[ \t]*,[ \t]*#{ELEMENT})*)?\]/
22
+ VALUE = /(?:#{BOOLEAN}|#{NIL}|#{ELEMENT}|#{ARRAY}|)/
23
+ PAIR = /(?:(#{KEY})[ \t]*=>[ \t]*(#{VALUE})|(\w+):[ \t]+(#{VALUE}))/
24
+ OPTIONS = /#{PAIR}(?:[ \t]*,[ \t]*#{PAIR})*/
25
+ COMMENT = /(#[^\n]*)?/
26
+
27
+ GEM_CALL = /^[ \t]*gem\(?[ \t]*#{QUOTED_GEM_NAME}(?:[ \t]*,[ \t]*#{REQUIREMENT_LIST})?(?:[ \t]*,[ \t]*(?<opts>#{OPTIONS}))?[ \t]*\)?[ \t]*#{COMMENT}$/
28
+ POD_CALL = /^[ \t]*pod\(?[ \t]*#{QUOTED_GEM_NAME}(?:[ \t]*,[ \t]*#{REQUIREMENT_LIST})?(?:[ \t]*,[ \t]*(?<opts>#{OPTIONS}))?[ \t]*\)?[ \t]*#{COMMENT}$/
29
+
30
+ SYMBOLS = /#{SYMBOL}([ \t]*,[ \t]*#{SYMBOL})*/
31
+ GROUP_CALL = /^(?<i1>[ \t]*)group\(?[ \t]*(?<grps>#{SYMBOLS})[ \t]*\)?[ \t]+do[ \t]*?\n(?<blk>.*?)\n^\k<i1>end[ \t]*$/m
32
+
33
+ GIT_CALL = /^(?<i1>[ \t]*)git[ \(][^\n]*?do[ \t]*?\n(?<blk>.*?)\n^\k<i1>end[ \t]*$/m
34
+
35
+ PATH_CALL = /^(?<i1>[ \t]*)path[ \(][^\n]*?do[ \t]*?\n(?<blk>.*?)\n^\k<i1>end[ \t]*$/m
36
+
37
+ GEMSPEC_CALL = /^[ \t]*gemspec(?:\(?[ \t]*(?<opts>#{OPTIONS}))?[ \t]*\)?[ \t]*$/
38
+
39
+ ADD_DEPENDENCY_CALL = /^[ \t]*\w+\.add(?<type>_runtime|_development)?_dependency\(?[ \t]*#{QUOTED_GEM_NAME}(?:[ \t]*,[ \t]*#{REQUIREMENTS})?[ \t]*\)?[ \t]*#{COMMENT}$/
40
+ ADD_POD_DEPENDENCY_CALL = /^[ \t]*\w+\.dependency\(?[ \t]*#{QUOTED_POD_NAME}(?:[ \t]*,[ \t]*#{REQUIREMENTS})?[ \t]*\)?[ \t]*#{COMMENT}$/
41
+
42
+ def self.options(string)
43
+ {}.tap do |hash|
44
+ return hash unless string
45
+ pairs = Hash[*string.match(OPTIONS).captures.compact]
46
+ pairs.each{|k,v| hash[key(k)] = value(v) }
47
+ end
48
+ end
49
+
50
+ def self.key(string)
51
+ string.tr(%(:"'), "")
52
+ end
53
+
54
+ def self.value(string)
55
+ case string
56
+ when ARRAY then values(string.tr("[]", ""))
57
+ when SYMBOL then string.tr(%(:"'), "").to_sym
58
+ when STRING then string.tr(%("'), "")
59
+ when BOOLEAN then string == "true"
60
+ when NIL then nil
61
+ end
62
+ end
63
+
64
+ def self.values(string)
65
+ string.strip.split(/[ \t]*,[ \t]*/).map{|v| value(v) }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,91 @@
1
+ require "bundler"
2
+ require "gemnasium/parser/patterns"
3
+
4
+ module Gemnasium
5
+ module Parser
6
+ class Podfile
7
+ attr_reader :content
8
+
9
+ def initialize(content)
10
+ @content = content
11
+ end
12
+
13
+ def dependencies
14
+ @dependencies ||= [].tap do |deps|
15
+ pod_matches.each do |match|
16
+ dep = dependency(match)
17
+ deps << dep if dep
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+ def pod_matches
24
+ @pod_matches ||= matches(Patterns::POD_CALL)
25
+ end
26
+
27
+ def matches(pattern)
28
+ [].tap{|m| content.scan(pattern){ m << Regexp.last_match } }
29
+ end
30
+
31
+ def dependency(match)
32
+ opts = Patterns.options(match["opts"])
33
+ clean!(match, opts)
34
+ name, reqs = match["name"], [match["req1"], match["req2"]].compact
35
+ Bundler::Dependency.new(name, reqs, opts).tap do |dep|
36
+ line = content.slice(0, match.begin(0)).count("\n") + 1
37
+ dep.instance_variable_set(:@line, line)
38
+ end
39
+ end
40
+
41
+ def groups(match)
42
+ group = group_matches.detect{|m| in_block?(match, m) }
43
+ group && Patterns.values(group[:grps])
44
+ end
45
+
46
+ def in_block?(inner, outer)
47
+ outer.begin(:blk) <= inner.begin(0) && outer.end(:blk) >= inner.end(0)
48
+ end
49
+
50
+ def group_matches
51
+ @group_matches ||= matches(Patterns::GROUP_CALL)
52
+ end
53
+
54
+ def git?(match, opts)
55
+ opts["git"] || in_git_block?(match)
56
+ end
57
+
58
+ def github?(match, opts)
59
+ opts["github"]
60
+ end
61
+
62
+ def in_git_block?(match)
63
+ git_matches.any?{|m| in_block?(match, m) }
64
+ end
65
+
66
+ def git_matches
67
+ @git_matches ||= matches(Patterns::GIT_CALL)
68
+ end
69
+
70
+ def path?(match, opts)
71
+ opts["path"] || in_path_block?(match)
72
+ end
73
+
74
+ def in_path_block?(match)
75
+ path_matches.any?{|m| in_block?(match, m) }
76
+ end
77
+
78
+ def path_matches
79
+ @path_matches ||= matches(Patterns::PATH_CALL)
80
+ end
81
+
82
+ def clean!(match, opts)
83
+ opts["group"] ||= opts.delete("groups")
84
+ opts["group"] ||= groups(match)
85
+ groups = Array(opts["group"]).flatten.compact
86
+ runtime = groups.empty? || !(groups & Parser.runtime_groups).empty?
87
+ opts["type"] ||= runtime ? :runtime : :development
88
+ end
89
+ end
90
+ end
91
+ end