Skip to content

Commit 386cef8

Browse files
authored
Merge pull request #1689 from khasinski/dependency-parsing-ruby
Add Gemfile.lock parsing for dependency parser
2 parents e150819 + f728e55 commit 386cef8

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
require 'bundler'
3+
require 'json'
4+
5+
module DependencyParser
6+
module Ruby
7+
class Parse
8+
attr_reader :errors
9+
10+
def initialize(content)
11+
@content = content
12+
@errors = []
13+
@direct = []
14+
end
15+
16+
def call
17+
validate_lockfile!(content)
18+
return if error?
19+
20+
@direct = Bundler::LockfileParser.new(content).specs.map do |spec|
21+
fetch_spec(name: spec.name, version: spec.version)
22+
end.compact
23+
end
24+
25+
def direct
26+
{ repos: @direct, language: "ruby" }
27+
end
28+
29+
def success?
30+
errors.empty?
31+
end
32+
33+
def error?
34+
errors.any?
35+
end
36+
37+
private
38+
39+
attr_reader :content
40+
41+
def validate_lockfile!(content)
42+
@errors << 'No specs found' unless Bundler::LockfileParser.new(content).specs.any?
43+
end
44+
45+
def fetch_spec(name:, version: nil)
46+
full_spec = fetcher.fetch_spec([name, version])
47+
{ name: full_spec.name, url: extract_url(full_spec), description: full_spec.description }
48+
rescue
49+
@errors << "Invalid spec #{name}"
50+
nil
51+
end
52+
53+
def extract_url(full_spec)
54+
url = full_spec.metadata["source_code_uri"] || full_spec.homepage
55+
url if url.include? "https://github.com/"
56+
end
57+
58+
def fetcher
59+
@fetcher ||= Bundler::Fetcher.new(Bundler::Source::Rubygems::Remote.new('https://rubygems.org'))
60+
end
61+
end
62+
end
63+
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
require_relative '../../lib/dependency_parser/ruby/parse'
5+
6+
class RubyParserTest < ActiveSupport::TestCase
7+
test "returns nothing on invalid Gemfile.lock" do
8+
parser = DependencyParser::Ruby::Parse.new("invalid")
9+
parser.call
10+
refute parser.success?
11+
assert_equal({ repos: [], language: "ruby" }, parser.direct)
12+
end
13+
14+
test "returns the list of gems for a small Gemfle" do
15+
gemfile_lock = <<~LOCKFILE
16+
GEM
17+
remote: https://rubygems.org/
18+
specs:
19+
solid_use_case (2.2.0)
20+
actionpack (= 6.1.4.1)
21+
activesupport (= 6.1.4.1)
22+
nio4r (~> 2.0)
23+
websocket-driver (>= 0.6.1)
24+
25+
PLATFORMS
26+
ruby
27+
28+
DEPENDENCIES
29+
actioncable
30+
31+
RUBY VERSION
32+
ruby 3.0.4p208
33+
34+
BUNDLED WITH
35+
2.3.21
36+
37+
LOCKFILE
38+
39+
parser = DependencyParser::Ruby::Parse.new(gemfile_lock)
40+
VCR.use_cassette("dependency_parser/rubygems") do
41+
parser.call
42+
end
43+
44+
gem_data = {
45+
repos:
46+
[{
47+
name: "solid_use_case",
48+
url: "https://github.com/mindeavor/solid_use_case",
49+
description: "Create use cases the way they were meant to be. Easily verify inputs at each step and seamlessly fail with custom error data and convenient pattern matching."
50+
}],
51+
language: "ruby"
52+
}
53+
54+
assert parser.success?
55+
assert_equal gem_data, parser.direct
56+
end
57+
end

test/vcr_cassettes/dependency_parser/rubygems.yml

Lines changed: 74 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)