ecosystems-bibliothecary 15.1.0 → 15.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +137 -120
  4. data/lib/bibliothecary/analyser.rb +4 -0
  5. data/lib/bibliothecary/parsers/actions.rb +4 -0
  6. data/lib/bibliothecary/parsers/bentoml.rb +4 -0
  7. data/lib/bibliothecary/parsers/bower.rb +4 -0
  8. data/lib/bibliothecary/parsers/cargo.rb +4 -0
  9. data/lib/bibliothecary/parsers/carthage.rb +4 -0
  10. data/lib/bibliothecary/parsers/clojars.rb +4 -0
  11. data/lib/bibliothecary/parsers/cocoapods.rb +4 -0
  12. data/lib/bibliothecary/parsers/cog.rb +4 -0
  13. data/lib/bibliothecary/parsers/conan.rb +4 -0
  14. data/lib/bibliothecary/parsers/conda.rb +4 -0
  15. data/lib/bibliothecary/parsers/cpan.rb +190 -2
  16. data/lib/bibliothecary/parsers/cran.rb +4 -0
  17. data/lib/bibliothecary/parsers/deno.rb +98 -0
  18. data/lib/bibliothecary/parsers/docker.rb +4 -0
  19. data/lib/bibliothecary/parsers/dub.rb +4 -0
  20. data/lib/bibliothecary/parsers/dvc.rb +4 -0
  21. data/lib/bibliothecary/parsers/elm.rb +4 -0
  22. data/lib/bibliothecary/parsers/go.rb +4 -0
  23. data/lib/bibliothecary/parsers/hackage.rb +55 -0
  24. data/lib/bibliothecary/parsers/haxelib.rb +4 -0
  25. data/lib/bibliothecary/parsers/hex.rb +89 -0
  26. data/lib/bibliothecary/parsers/homebrew.rb +4 -0
  27. data/lib/bibliothecary/parsers/julia.rb +55 -0
  28. data/lib/bibliothecary/parsers/luarocks.rb +4 -0
  29. data/lib/bibliothecary/parsers/maven.rb +4 -0
  30. data/lib/bibliothecary/parsers/meteor.rb +4 -0
  31. data/lib/bibliothecary/parsers/mlflow.rb +4 -0
  32. data/lib/bibliothecary/parsers/nimble.rb +4 -0
  33. data/lib/bibliothecary/parsers/nix.rb +205 -0
  34. data/lib/bibliothecary/parsers/npm.rb +4 -0
  35. data/lib/bibliothecary/parsers/nuget.rb +4 -0
  36. data/lib/bibliothecary/parsers/ollama.rb +4 -0
  37. data/lib/bibliothecary/parsers/packagist.rb +4 -0
  38. data/lib/bibliothecary/parsers/pub.rb +4 -0
  39. data/lib/bibliothecary/parsers/pypi.rb +9 -0
  40. data/lib/bibliothecary/parsers/rubygems.rb +4 -0
  41. data/lib/bibliothecary/parsers/shard.rb +4 -0
  42. data/lib/bibliothecary/parsers/swift_pm.rb +4 -0
  43. data/lib/bibliothecary/parsers/vcpkg.rb +4 -0
  44. data/lib/bibliothecary/version.rb +1 -1
  45. data/lib/bibliothecary.rb +7 -0
  46. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78f959cefcb80f16e6e56d087449ec23576dc7177b5f4a2f0448654f9d7dd022
4
- data.tar.gz: fe94e344c4d43098281d613bfaf6168ec4464766fddedc61a97a9028f16f7174
3
+ metadata.gz: c1399499146bc9abb6b2053d1e842cff2a33e05166f4fca7fed6fba222146f5e
4
+ data.tar.gz: 2b1460ba23303796a1b73bfa445fee11771857f5270effce6da04e6ed4ff920f
5
5
  SHA512:
6
- metadata.gz: 51e844390f10db2667ef885ad5a0ce300d33b8845808b4f2511869c03512176ce196f8de51c04d11fcfc429ec106849aa5e090800a3ae3aa151f989e8cfe0270
7
- data.tar.gz: 0f3a788b617189160bb45f9d73afc4e78ded47a010112a7369c28853a1dd842c3922e1dd7e22c2a62cf494ebaa6a550cf8e009c387e1c58fa27fe6f9bfedc724
6
+ metadata.gz: 2ab9631fd2e5472d6a60784685252961f75fcae716561d1e7bd6c1bc7f87bba18f346988b26f787cc13bd666fc06a99db133926726880ee983e01fbe4f64a5d9
7
+ data.tar.gz: 1dc6d4efca6e8b643ca7c76e495a6b40364169fc8b51d238f2708e5da1ec8d04515adafec8d2042951088729258b3552a547473c93a59ae3f12c7a89ff681065
data/CHANGELOG.md CHANGED
@@ -13,6 +13,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
13
 
14
14
  ### Removed
15
15
 
16
+ ## [15.2.0]
17
+
18
+ ### Added
19
+
20
+ - Deno parser for deno.json, deno.jsonc, and deno.lock files (supports npm: and jsr: specifiers)
21
+ - Nix parser for flake.nix, flake.lock, nix/sources.json (niv), and npins/sources.json (npins)
22
+ - Gleam support in Hex parser for gleam.toml and manifest.toml
23
+ - Rebar3 support in Hex parser for rebar.lock
24
+ - Julia Project.toml and Manifest.toml support
25
+ - Hackage cabal.project.freeze support
26
+
27
+ ## [15.1.1]
28
+
29
+ ### Added
30
+
31
+ - CPAN: cpanfile parser for Perl dependency declarations
32
+ - CPAN: cpanfile.snapshot parser for Carton lockfiles
33
+ - CPAN: Makefile.PL parser for ExtUtils::MakeMaker build scripts
34
+ - CPAN: Build.PL parser for Module::Build scripts
35
+
36
+ ### Changed
37
+
38
+ - CPAN: META.json and META.yml are now classified as lockfiles (they are generated, not hand-written)
39
+
16
40
  ## [15.1.0]
17
41
 
18
42
  ### Added
data/README.md CHANGED
@@ -44,160 +44,177 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
44
44
 
45
45
  ## Supported package manager file formats
46
46
 
47
- - npm
48
- - package.json
49
- - package-lock.json
50
- - npm-shrinkwrap.json
51
- - yarn.lock
52
- - pnpm-lock.yaml
53
- - bun.lock
54
- - npm-ls.json
55
- - Maven
56
- - pom.xml
57
- - ivy.xml
58
- - build.gradle
59
- - build.gradle.kts
60
- - gradle-dependencies-q.txt
61
- - maven-resolved-dependencies.txt
62
- - sbt-update-full.txt
63
- - maven-dependency-tree.txt
64
- - maven-dependency-tree.dot
65
- - gradle.lockfile
66
- - verification-metadata.xml
67
- - RubyGems
68
- - Gemfile
69
- - Gemfile.lock
70
- - gems.rb
71
- - gems.locked
72
- - *.gemspec
73
- - Packagist
74
- - composer.json
75
- - composer.lock
76
- - PyPi
77
- - setup.py
78
- - req*.txt
79
- - req*.pip
80
- - requirements/*.txt
81
- - requirements/*.pip
82
- - requirements.frozen
83
- - Pipfile
84
- - Pipfile.lock
85
- - pyproject.toml
86
- - poetry.lock
87
- - uv.lock
88
- - pylock.toml
89
- - pdm.lock
90
- - pip-resolved-dependencies.txt
91
- - pip-dependency-graph.json
92
- - Nuget
93
- - packages.config
94
- - packages.lock.json
95
- - Project.json
96
- - Project.lock.json
97
- - *.nuspec
98
- - paket.lock
99
- - *.csproj
100
- - project.assets.json
101
- - \*.deps.json
102
- - Bower
103
- - bower.json
104
- - BentoML
105
- - bentofile.yaml
106
- - CPAN
107
- - META.json
108
- - META.yml
109
- - CocoaPods
110
- - Podfile
111
- - Podfile.lock
112
- - *.podspec
113
- - *.podspec.json
47
+ - Actions
48
+ - action.yml
49
+ - action.yaml
50
+ - .github/workflows/\*.yml
51
+ - .github/workflows/\*.yaml
114
52
  - Anaconda
115
53
  - environment.yml
116
54
  - environment.yaml
55
+ - BentoML
56
+ - bentofile.yaml
57
+ - Bower
58
+ - bower.json
59
+ - Cargo
60
+ - Cargo.toml
61
+ - Cargo.lock
62
+ - Carthage
63
+ - Cartfile
64
+ - Cartfile.private
65
+ - Cartfile.resolved
117
66
  - Clojars
118
67
  - project.clj
68
+ - CocoaPods
69
+ - Podfile
70
+ - \*.podspec
71
+ - Podfile.lock
72
+ - \*.podspec.json
119
73
  - Cog
120
74
  - cog.yaml
121
75
  - Conan
122
76
  - conanfile.py
123
77
  - conanfile.txt
124
78
  - conan.lock
125
- - Meteor
126
- - versions.json
127
- - MLflow
128
- - MLmodel
79
+ - CPAN
80
+ - META.json
81
+ - META.yml
82
+ - cpanfile
83
+ - cpanfile.snapshot
84
+ - Makefile.PL
85
+ - Build.PL
129
86
  - CRAN
130
87
  - DESCRIPTION
131
88
  - renv.lock
132
- - Cargo
133
- - Cargo.toml
134
- - Cargo.lock
135
- - Hex
136
- - mix.exs
137
- - mix.lock
138
- - Swift
139
- - Package.swift
140
- - Package.resolved
141
- - Pub
142
- - pubspec.yaml
143
- - pubspec.lock
144
- - Carthage
145
- - Cartfile
146
- - Cartfile.private
147
- - Cartfile.resolved
89
+ - Deno
90
+ - deno.json
91
+ - deno.jsonc
92
+ - deno.lock
93
+ - Docker
94
+ - docker-compose\*.yml
95
+ - Dockerfile
148
96
  - Dub
149
97
  - dub.json
150
98
  - dub.sdl
151
- - Julia
152
- - REQUIRE
153
- - Shards
154
- - shard.yml
155
- - shard.lock
99
+ - DVC
100
+ - dvc.yaml
101
+ - Elm
102
+ - elm-package.json
103
+ - elm_dependencies.json
104
+ - elm-stuff/exact-dependencies.json
156
105
  - Go
106
+ - go.mod
107
+ - go.sum
157
108
  - glide.yaml
158
109
  - glide.lock
159
- - Godeps
160
110
  - Godeps/Godeps.json
111
+ - Godeps
161
112
  - vendor/manifest
162
113
  - vendor/vendor.json
163
114
  - Gopkg.toml
164
115
  - Gopkg.lock
165
- - go.mod
166
- - go.sum
167
116
  - go-resolved-dependencies.json
168
- - Elm
169
- - elm-package.json
170
- - elm_dependencies.json
171
- - elm-stuff/exact-dependencies.json
172
- - Haxelib
173
- - haxelib.json
174
117
  - Hackage
175
118
  - \*.cabal
176
- - cabal.config
119
+ - \*cabal.config
177
120
  - stack.yaml.lock
178
- - Actions
179
- - action.yml
180
- - action.yaml
181
- - .github/workflows/*.yml
182
- - .github/workflows/*.yaml
183
- - Docker
184
- - Dockerfile
185
- - docker-compose*.yml
186
- - docker-compose*.yaml
187
- - DVC
188
- - dvc.yaml
189
- - Vcpkg
190
- - vcpkg.json
191
- - _generated-vcpkg-list.json
121
+ - cabal.project.freeze
122
+ - Haxelib
123
+ - haxelib.json
124
+ - Hex
125
+ - mix.exs
126
+ - mix.lock
127
+ - gleam.toml
128
+ - manifest.toml
129
+ - rebar.lock
192
130
  - Homebrew
193
131
  - Brewfile
194
132
  - Brewfile.lock.json
195
- - Ollama
196
- - Modelfile
197
- - Nimble
198
- - \*.nimble
133
+ - Julia
134
+ - REQUIRE
135
+ - Project.toml
136
+ - Manifest.toml
199
137
  - LuaRocks
200
138
  - \*.rockspec
139
+ - Maven
140
+ - ivy.xml
141
+ - pom.xml
142
+ - build.gradle
143
+ - build.gradle.kts
144
+ - gradle-dependencies-q.txt
145
+ - maven-resolved-dependencies.txt
146
+ - sbt-update-full.txt
147
+ - maven-dependency-tree.txt
148
+ - maven-dependency-tree.dot
149
+ - gradle.lockfile
150
+ - verification-metadata.xml
151
+ - Meteor
152
+ - versions.json
153
+ - MLflow
154
+ - MLmodel
155
+ - Nimble
156
+ - \*.nimble
157
+ - Nix
158
+ - flake.nix
159
+ - flake.lock
160
+ - nix/sources.json
161
+ - npins/sources.json
162
+ - npm
163
+ - package.json
164
+ - package-lock.json
165
+ - npm-shrinkwrap.json
166
+ - yarn.lock
167
+ - pnpm-lock.yaml
168
+ - bun.lock
169
+ - npm-ls.json
170
+ - Nuget
171
+ - Project.json
172
+ - Project.lock.json
173
+ - packages.lock.json
174
+ - packages.config
175
+ - \*.nuspec
176
+ - \*.csproj
177
+ - paket.lock
178
+ - project.assets.json
179
+ - \*.deps.json
180
+ - Ollama
181
+ - Modelfile
182
+ - Packagist
183
+ - composer.json
184
+ - composer.lock
185
+ - Pub
186
+ - pubspec.yaml
187
+ - pubspec.lock
188
+ - PyPi
189
+ - setup.py
190
+ - requirements\*.txt
191
+ - requirements\*.pip
192
+ - requirements\*.in
193
+ - requirements.frozen
194
+ - Pipfile
195
+ - Pipfile.lock
196
+ - pyproject.toml
197
+ - poetry.lock
198
+ - uv.lock
199
+ - pylock.toml
200
+ - pdm.lock
201
+ - pip-resolved-dependencies.txt
202
+ - pip-dependency-graph.json
203
+ - RubyGems
204
+ - Gemfile
205
+ - Gemfile.lock
206
+ - gems.rb
207
+ - gems.locked
208
+ - \*.gemspec
209
+ - Shards
210
+ - shard.yml
211
+ - shard.lock
212
+ - Swift
213
+ - Package.swift
214
+ - Package.resolved
215
+ - Vcpkg
216
+ - vcpkg.json
217
+ - _generated-vcpkg-list.json
201
218
 
202
219
  ## Development
203
220
 
@@ -43,6 +43,10 @@ module Bibliothecary
43
43
  @platform_name ||= name.to_s.split("::").last.downcase.freeze
44
44
  end
45
45
 
46
+ def file_patterns
47
+ []
48
+ end
49
+
46
50
  def map_dependencies(hash, key, type, source = nil)
47
51
  hash.fetch(key, []).map do |name, requirement|
48
52
  Dependency.new(
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
 
8
8
  WORKFLOW_REGEX = /^\.github\/workflows\/.*.y(a)?ml/
9
9
 
10
+ def self.file_patterns
11
+ ["action.yml", "action.yaml", ".github/workflows/*.yml", ".github/workflows/*.yaml"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filenames("action.yml","action.yaml") => {
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class BentoML
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["bentofile.yaml"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("bentofile.yaml") => {
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
  class Bower
8
8
  include Bibliothecary::Analyser
9
9
 
10
+ def self.file_patterns
11
+ ["bower.json"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filename("bower.json") => {
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class Cargo
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["Cargo.toml", "Cargo.lock"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("Cargo.toml") => {
@@ -15,6 +15,10 @@ module Bibliothecary
15
15
  # Group 4: unquoted requirement (e.g., >= 1.0, ~> 2.0)
16
16
  CARTFILE_REGEXP = /^(github|git|binary)\s+"([^"]+)"(?:\s+(?:"([^"]+)"|((?:>=|<=|~>|==|>|<)\s*[\d.]+)))?/
17
17
 
18
+ def self.file_patterns
19
+ ["Cartfile", "Cartfile.private", "Cartfile.resolved"]
20
+ end
21
+
18
22
  def self.mapping
19
23
  {
20
24
  match_filename("Cartfile") => {
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
  # Name can be like: org.clojure/clojure, cheshire, ring/ring-defaults
8
8
  DEPENDENCY_REGEXP = %r{\[([a-zA-Z0-9_./\-]+)\s+"([^"]+)"\]}
9
9
 
10
+ def self.file_patterns
11
+ ["project.clj"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filename("project.clj") => {
@@ -17,6 +17,10 @@ module Bibliothecary
17
17
  # Podspec pattern: .dependency "Name", "version"
18
18
  PODSPEC_DEPENDENCY = /\.dependency\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?/
19
19
 
20
+ def self.file_patterns
21
+ ["Podfile", "*.podspec", "Podfile.lock", "*.podspec.json"]
22
+ end
23
+
20
24
  def self.mapping
21
25
  {
22
26
  match_filename("Podfile") => {
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class Cog
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["cog.yaml"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("cog.yaml") => {
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class Conan
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["conanfile.py", "conanfile.txt", "conan.lock"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("conanfile.py") => {
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
  class Conda
8
8
  include Bibliothecary::Analyser
9
9
 
10
+ def self.file_patterns
11
+ ["environment.yml", "environment.yaml"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filename("environment.yml") => {
@@ -8,16 +8,36 @@ module Bibliothecary
8
8
  class CPAN
9
9
  include Bibliothecary::Analyser
10
10
 
11
+ def self.file_patterns
12
+ ["META.json", "META.yml", "cpanfile", "cpanfile.snapshot", "Makefile.PL", "Build.PL"]
13
+ end
14
+
11
15
  def self.mapping
12
16
  {
13
17
  match_filename("META.json", case_insensitive: true) => {
14
- kind: "manifest",
18
+ kind: "lockfile",
15
19
  parser: :parse_json_manifest,
16
20
  },
17
21
  match_filename("META.yml", case_insensitive: true) => {
18
- kind: "manifest",
22
+ kind: "lockfile",
19
23
  parser: :parse_yaml_manifest,
20
24
  },
25
+ match_filename("cpanfile", case_insensitive: true) => {
26
+ kind: "manifest",
27
+ parser: :parse_cpanfile,
28
+ },
29
+ match_filename("cpanfile.snapshot", case_insensitive: true) => {
30
+ kind: "lockfile",
31
+ parser: :parse_cpanfile_snapshot,
32
+ },
33
+ match_filename("Makefile.PL", case_insensitive: true) => {
34
+ kind: "manifest",
35
+ parser: :parse_makefile_pl,
36
+ },
37
+ match_filename("Build.PL", case_insensitive: true) => {
38
+ kind: "manifest",
39
+ parser: :parse_build_pl,
40
+ },
21
41
  }
22
42
  end
23
43
 
@@ -35,6 +55,174 @@ module Bibliothecary
35
55
  dependencies = map_dependencies(manifest, "requires", "runtime", options.fetch(:filename, nil))
36
56
  ParserResult.new(dependencies: dependencies)
37
57
  end
58
+
59
+ def self.parse_cpanfile(file_contents, options: {})
60
+ filename = options.fetch(:filename, nil)
61
+ dependencies = []
62
+ current_phase = "runtime"
63
+ current_feature = nil
64
+
65
+ file_contents.each_line do |line|
66
+ line = line.strip
67
+
68
+ # Track phase changes: on 'test' => sub {
69
+ if line =~ /\bon\s+['"](\w+)['"]\s*=>/
70
+ current_phase = $1
71
+ next
72
+ end
73
+
74
+ # Track feature blocks: feature 'name', 'desc' => sub {
75
+ if line =~ /\bfeature\s+['"]([\w-]+)['"]/
76
+ current_feature = $1
77
+ next
78
+ end
79
+
80
+ # End of block - reset to defaults
81
+ if line =~ /^\s*\};\s*$/
82
+ current_phase = "runtime"
83
+ current_feature = nil
84
+ next
85
+ end
86
+
87
+ # Parse dependency declarations
88
+ # requires 'Module::Name', 'version';
89
+ # requires 'Module::Name';
90
+ # recommends 'Module::Name', 'version';
91
+ if line =~ /\b(requires|recommends|suggests|conflicts)\s+['"]([^'"]+)['"](?:\s*,\s*['"]?([^'";]+)['"]?)?/
92
+ dep_type = $1
93
+ name = $2
94
+ version = $3&.strip || "*"
95
+
96
+ # Map cpanfile phases to our types
97
+ type = case current_phase
98
+ when "test" then "test"
99
+ when "develop" then "develop"
100
+ when "build" then "build"
101
+ when "configure" then "build"
102
+ else dep_type == "requires" ? "runtime" : dep_type
103
+ end
104
+
105
+ dependencies << Dependency.new(
106
+ name: name,
107
+ requirement: version,
108
+ type: type,
109
+ platform: "cpan",
110
+ source: filename
111
+ )
112
+ end
113
+ end
114
+
115
+ ParserResult.new(dependencies: dependencies)
116
+ end
117
+
118
+ def self.parse_cpanfile_snapshot(file_contents, options: {})
119
+ filename = options.fetch(:filename, nil)
120
+ dependencies = []
121
+
122
+ file_contents.each_line do |line|
123
+ # Match distribution header: Module-Name-1.23
124
+ if (match = line.match(/^ (\S+)-v?([\d._]+)$/))
125
+ dist_name = match[1].gsub("-", "::")
126
+ version = match[2]
127
+ dependencies << Dependency.new(
128
+ name: dist_name,
129
+ requirement: version,
130
+ type: "runtime",
131
+ platform: "cpan",
132
+ source: filename
133
+ )
134
+ end
135
+ end
136
+
137
+ ParserResult.new(dependencies: dependencies)
138
+ end
139
+
140
+ # Parse Makefile.PL (ExtUtils::MakeMaker format)
141
+ # Looks for PREREQ_PM, BUILD_REQUIRES, TEST_REQUIRES, CONFIGURE_REQUIRES
142
+ def self.parse_makefile_pl(file_contents, options: {})
143
+ filename = options.fetch(:filename, nil)
144
+ dependencies = []
145
+
146
+ # Map of hash key names to dependency types
147
+ type_mapping = {
148
+ "PREREQ_PM" => "runtime",
149
+ "BUILD_REQUIRES" => "build",
150
+ "TEST_REQUIRES" => "test",
151
+ "CONFIGURE_REQUIRES" => "build",
152
+ }
153
+
154
+ type_mapping.each do |key, type|
155
+ deps = extract_perl_hash(file_contents, key)
156
+ deps.each do |name, version|
157
+ dependencies << Dependency.new(
158
+ name: name,
159
+ requirement: version,
160
+ type: type,
161
+ platform: "cpan",
162
+ source: filename
163
+ )
164
+ end
165
+ end
166
+
167
+ ParserResult.new(dependencies: dependencies)
168
+ end
169
+
170
+ # Parse Build.PL (Module::Build format)
171
+ # Looks for requires, build_requires, test_requires, configure_requires
172
+ def self.parse_build_pl(file_contents, options: {})
173
+ filename = options.fetch(:filename, nil)
174
+ dependencies = []
175
+
176
+ # Map of hash key names to dependency types
177
+ type_mapping = {
178
+ "requires" => "runtime",
179
+ "build_requires" => "build",
180
+ "test_requires" => "test",
181
+ "configure_requires" => "build",
182
+ "recommends" => "runtime",
183
+ }
184
+
185
+ type_mapping.each do |key, type|
186
+ deps = extract_perl_hash(file_contents, key)
187
+ deps.each do |name, version|
188
+ dependencies << Dependency.new(
189
+ name: name,
190
+ requirement: version,
191
+ type: type,
192
+ platform: "cpan",
193
+ source: filename
194
+ )
195
+ end
196
+ end
197
+
198
+ ParserResult.new(dependencies: dependencies)
199
+ end
200
+
201
+ # Extract a Perl hash from source code
202
+ # Handles patterns like: KEY => { 'Module' => '1.0', ... }
203
+ def self.extract_perl_hash(content, key)
204
+ deps = {}
205
+
206
+ # Match the hash assignment: KEY => { ... }
207
+ # Use word boundary to avoid matching configure_requires when looking for requires
208
+ pattern = /(?:^|[^\w])#{Regexp.escape(key)}\s*=>\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/m
209
+
210
+ if (match = content.match(pattern))
211
+ hash_content = match[1]
212
+
213
+ # Extract 'Module::Name' => 'version' or 'Module::Name' => version patterns
214
+ hash_content.scan(/['"]([^'"]+)['"]\s*=>\s*['"]?([^'",}\s]+)['"]?/) do |name, version|
215
+ # Skip perl version requirements and non-module entries
216
+ next if name == "perl"
217
+
218
+ # Normalize version: 0 means any version
219
+ version = "*" if version == "0"
220
+ deps[name] = version
221
+ end
222
+ end
223
+
224
+ deps
225
+ end
38
226
  end
39
227
  end
40
228
  end
@@ -9,6 +9,10 @@ module Bibliothecary
9
9
 
10
10
  REQUIRE_REGEXP = /([a-zA-Z0-9\-_.]+)\s?\(?([><=\s\d.,]+)?\)?/
11
11
 
12
+ def self.file_patterns
13
+ ["DESCRIPTION", "renv.lock"]
14
+ end
15
+
12
16
  def self.mapping
13
17
  {
14
18
  match_filename("DESCRIPTION", case_insensitive: true) => {