1

I'm trying to parse the following Gemfile.lock to include ALL Gems (direct and indirect dependencies) out of GEM specs:

GEM
  remote: http://rubygems.org/
  specs:
    coderay (1.1.3)
    domain_name (0.5.20190701)
      unf (>= 0.0.5, < 1.0.0)
    http-accept (1.7.0)
    http-cookie (1.0.4)
      domain_name (~> 0.5)
    json (2.5.1)
    method_source (1.0.0)
    mime-types (3.3.1)
      mime-types-data (~> 3.2015)
    mime-types-data (3.2021.0704)
    netrc (0.11.0)
    rest-client (2.1.0)
      http-accept (>= 1.7.0, < 2.0)
      http-cookie (>= 1.0.2, < 2.0)
      mime-types (>= 1.16, < 4.0)
      netrc (~> 0.8)
    unf (0.1.4)
      unf_ext
    unf_ext (0.0.7.7)
    yaml (0.1.1)

PLATFORMS
  ruby
  x86_64-darwin-20

DEPENDENCIES
  json
  rest-client
  yaml

RUBY VERSION
   ruby 2.7.3p183

BUNDLED WITH
   2.2.23

But using my function I can only get the direct dependencies without their indirect ones. e.g: direct dependency: http-cookie (1.0.4)... indirect dependency: domain_name (~> 0.5)

My code:

require 'bundler'

def gemlock(file_path)
  file = file_path
  gemlock_array = []

  context = Bundler::LockfileParser.new(Bundler.read_file(file))

  # Gems
  context.specs.each do |spec|
    name = spec.name
    version = spec.version.to_s

    gemlock_array << {'name' => name, 'version' => version}
  end
  puts gemlock_array
end

gemlock('Gemfile.lock')

I'm getting the following hash back:

{"name"=>"coderay", "version"=>"1.1.3"}
{"name"=>"domain_name", "version"=>"0.5.20190701"}
{"name"=>"http-accept", "version"=>"1.7.0"}
...

As you can see indirect dependencies were automatically ignored! But I still need to get it.

I don't have any experience with bundler and don't know how to solve this problem. Any help in this matter would be much appreciated!

Thanks in advance.

ivey221
  • 95
  • 7

1 Answers1

2

indirect dependencies were automatically ignored

It would have helped if you'd included the full output. I just ran it myself, and you get:

[{"name"=>"coderay", "version"=>"1.1.3"},
 {"name"=>"domain_name", "version"=>"0.5.20190701"},
 {"name"=>"http-accept", "version"=>"1.7.0"},
 {"name"=>"http-cookie", "version"=>"1.0.4"},
 {"name"=>"json", "version"=>"2.5.1"},
 {"name"=>"method_source", "version"=>"1.0.0"},
 {"name"=>"mime-types", "version"=>"3.3.1"},
 {"name"=>"mime-types-data", "version"=>"3.2021.0704"},
 {"name"=>"netrc", "version"=>"0.11.0"},
 {"name"=>"rest-client", "version"=>"2.1.0"},
 {"name"=>"unf", "version"=>"0.1.4"}, # <------ !!!!!!!!!!!!
 {"name"=>"unf_ext", "version"=>"0.0.7.7"},
 {"name"=>"yaml", "version"=>"0.1.1"}]

So in summary, the indirect dependency of domain_name, i.e. unf, which is presumably what you were referring to, is included further down the list. Your code already works exactly as you intended.

One minor point, though: You can simplify the implementation a little by doing this:

gemlock_array = context.specs.map { |s| {'name' => s.name, 'version' => s.version.to_s} }

In ruby, assigning a temporary array and appending to it within a .each loop is usually sub-optimal. Use map instead, to just construct the array directly.

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • Also: Kudos for using `Bundler::LockfileParser` instead of trying to build your own hacky solution with regex!! It's a breath of fresh air to see someone using parsers for their intended use case on StackOverflow instead of trying to reinvent a wheel (poorly) with regex :D – Tom Lord Jul 27 '21 at 17:08
  • Hey Tom! I've been trying for a couple of hours with it and don't know why I couldn't notice that they were already included :D Yeah, honestly I'm not experienced with regex that's why I was searching for an easier method until I found this one ;) and I like it a lot! Many thanks! – ivey221 Jul 27 '21 at 17:24