Module: Gem::Skill::Lockfile

Defined in:
lib/gem/skill/lockfile.rb

Overview

Parses Gemfile.lock to extract direct dependency gem name/version pairs. Direct deps are listed in the DEPENDENCIES section; versions come from specs.

Class Method Summary collapse

Class Method Details

.gems(lockfile_path = "Gemfile.lock") ⇒ Object

Raises:



7
8
9
10
11
# File 'lib/gem/skill/lockfile.rb', line 7

def self.gems(lockfile_path = "Gemfile.lock")
  raise Error, "Gemfile.lock not found at #{lockfile_path}" unless File.exist?(lockfile_path)

  parse(File.read(lockfile_path))
end

.parse(content) ⇒ Object



13
14
15
16
17
18
19
# File 'lib/gem/skill/lockfile.rb', line 13

def self.parse(content)
  specs  = parse_specs(content)
  direct = parse_direct_names(content) + parse_path_dep_names(content)
  direct.uniq.each_with_object({}) do |name, hash|
    hash[name] = specs[name] if specs.key?(name)
  end
end

.parse_direct_names(content) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/gem/skill/lockfile.rb', line 66

def self.parse_direct_names(content)
  in_deps = false
  names   = []

  content.each_line do |line|
    if line.strip == "DEPENDENCIES"
      in_deps = true
      next
    elsif in_deps && line =~ /\A  (\S+)/
      # Strip version constraints from dep names like "ruby_llm (>= 1.0)"
      names << Regexp.last_match(1)
    elsif in_deps && !line.strip.empty?
      in_deps = false
    end
  end

  names
end

.parse_path_dep_names(content) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/gem/skill/lockfile.rb', line 39

def self.parse_path_dep_names(content)
  in_path  = false
  in_specs = false
  in_gem   = false
  names    = []

  content.each_line do |line|
    if line.strip == "PATH"
      in_path = true
      in_specs = false
      in_gem   = false
    elsif in_path && line.strip == "specs:"
      in_specs = true
    elsif in_path && in_specs && line =~ /\A {4}\S/
      in_gem = true
    elsif in_path && in_specs && in_gem && line =~ /\A {6}(\S+)/
      names << Regexp.last_match(1)
    elsif in_path && !line.start_with?(" ") && !line.strip.empty?
      in_path  = false
      in_specs = false
      in_gem   = false
    end
  end

  names
end

.parse_specs(content) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/gem/skill/lockfile.rb', line 21

def self.parse_specs(content)
  in_specs = false
  specs    = {}

  content.each_line do |line|
    if line.strip == "specs:"
      in_specs = true
      next
    elsif in_specs && line =~ /\A {4}(\S+) \(([^)]+)\)/
      specs[Regexp.last_match(1)] = Regexp.last_match(2)
    elsif in_specs && line !~ /\A /
      in_specs = false
    end
  end

  specs
end