ecosystems-bibliothecary 15.0.1 → 15.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 508531974284b48df7e084f6d0c7c3a4762f7cf5f1af7ed624c86f469a413bfb
4
- data.tar.gz: 2442b323ba7169decede7da3c68284402440613dd1091fec7ff1dee8c5bdbe56
3
+ metadata.gz: 49988f4a70c1d5dcf6b7c05bffba8d885b3e63f04545f0c524d32b46de0804f2
4
+ data.tar.gz: 121b4e75f934770b9967b0e3b2427ddc1dc6aaaa19f8ed0d72f1001e50d4d575
5
5
  SHA512:
6
- metadata.gz: bf3b5d65d9d02cbafa1e86f04652cdb7f00ffcefd04f55410850bd904e4e1f1e09bf95cd4893270cd93b5219af6fe14c1cc4e7954fe21bd1deb0979b450a37b4
7
- data.tar.gz: 24d881e490fcd2dc28647a3936015d552ded40a0567a11f28fea160e277a3b70f736d9a640b3f3773d7aec5a6c8622d8a10349b2d0c820a0338d31cce9d0ba07
6
+ metadata.gz: 227adbf47ce52d6a9bb70be154f0cc204f5f327b68adeb8c91fd2e668036359eebd07d3fb940f0d4eef00128af04fb2073304147b9285589917efef1b1d5d5cf
7
+ data.tar.gz: ca3b36ddfa71702dae9737500dcf09ad6a9e6c4daf5758738c793614bcdcbd730a50083d081ee45a899536b0f01c1bd7d4eef90117cc3c1aeee6063a780e0b3f
data/CHANGELOG.md CHANGED
@@ -13,6 +13,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
13
 
14
14
  ### Removed
15
15
 
16
+ ## [15.1.1]
17
+
18
+ ### Added
19
+
20
+ - CPAN: cpanfile parser for Perl dependency declarations
21
+ - CPAN: cpanfile.snapshot parser for Carton lockfiles
22
+ - CPAN: Makefile.PL parser for ExtUtils::MakeMaker build scripts
23
+ - CPAN: Build.PL parser for Module::Build scripts
24
+
25
+ ### Changed
26
+
27
+ - CPAN: META.json and META.yml are now classified as lockfiles (they are generated, not hand-written)
28
+
29
+ ## [15.1.0]
30
+
31
+ ### Added
32
+
33
+ - pdm.lock parser for Python PDM package manager
34
+ - renv.lock parser for R package management
35
+ - stack.yaml.lock parser for Haskell Stack
36
+ - gradle.lockfile parser for Gradle dependency locking
37
+ - .deps.json parser for .NET runtime dependencies
38
+ - verification-metadata.xml parser for Gradle dependency verification
39
+ - Nimble parser for Nim package manager (.nimble files)
40
+ - LuaRocks parser for Lua package manager (.rockspec files)
41
+
16
42
  ## [15.0.1]
17
43
 
18
44
  ### Changed
data/README.md CHANGED
@@ -62,6 +62,8 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
62
62
  - sbt-update-full.txt
63
63
  - maven-dependency-tree.txt
64
64
  - maven-dependency-tree.dot
65
+ - gradle.lockfile
66
+ - verification-metadata.xml
65
67
  - RubyGems
66
68
  - Gemfile
67
69
  - Gemfile.lock
@@ -84,6 +86,7 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
84
86
  - poetry.lock
85
87
  - uv.lock
86
88
  - pylock.toml
89
+ - pdm.lock
87
90
  - pip-resolved-dependencies.txt
88
91
  - pip-dependency-graph.json
89
92
  - Nuget
@@ -95,6 +98,7 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
95
98
  - paket.lock
96
99
  - *.csproj
97
100
  - project.assets.json
101
+ - \*.deps.json
98
102
  - Bower
99
103
  - bower.json
100
104
  - BentoML
@@ -102,6 +106,10 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
102
106
  - CPAN
103
107
  - META.json
104
108
  - META.yml
109
+ - cpanfile
110
+ - cpanfile.snapshot
111
+ - Makefile.PL
112
+ - Build.PL
105
113
  - CocoaPods
106
114
  - Podfile
107
115
  - Podfile.lock
@@ -124,6 +132,7 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
124
132
  - MLmodel
125
133
  - CRAN
126
134
  - DESCRIPTION
135
+ - renv.lock
127
136
  - Cargo
128
137
  - Cargo.toml
129
138
  - Cargo.lock
@@ -169,6 +178,7 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
169
178
  - Hackage
170
179
  - \*.cabal
171
180
  - cabal.config
181
+ - stack.yaml.lock
172
182
  - Actions
173
183
  - action.yml
174
184
  - action.yaml
@@ -188,6 +198,10 @@ All available config options are in: https://github.com/ecosyste-ms/bibliothecar
188
198
  - Brewfile.lock.json
189
199
  - Ollama
190
200
  - Modelfile
201
+ - Nimble
202
+ - \*.nimble
203
+ - LuaRocks
204
+ - \*.rockspec
191
205
 
192
206
  ## Development
193
207
 
@@ -11,13 +11,29 @@ module Bibliothecary
11
11
  def self.mapping
12
12
  {
13
13
  match_filename("META.json", case_insensitive: true) => {
14
- kind: "manifest",
14
+ kind: "lockfile",
15
15
  parser: :parse_json_manifest,
16
16
  },
17
17
  match_filename("META.yml", case_insensitive: true) => {
18
- kind: "manifest",
18
+ kind: "lockfile",
19
19
  parser: :parse_yaml_manifest,
20
20
  },
21
+ match_filename("cpanfile", case_insensitive: true) => {
22
+ kind: "manifest",
23
+ parser: :parse_cpanfile,
24
+ },
25
+ match_filename("cpanfile.snapshot", case_insensitive: true) => {
26
+ kind: "lockfile",
27
+ parser: :parse_cpanfile_snapshot,
28
+ },
29
+ match_filename("Makefile.PL", case_insensitive: true) => {
30
+ kind: "manifest",
31
+ parser: :parse_makefile_pl,
32
+ },
33
+ match_filename("Build.PL", case_insensitive: true) => {
34
+ kind: "manifest",
35
+ parser: :parse_build_pl,
36
+ },
21
37
  }
22
38
  end
23
39
 
@@ -35,6 +51,174 @@ module Bibliothecary
35
51
  dependencies = map_dependencies(manifest, "requires", "runtime", options.fetch(:filename, nil))
36
52
  ParserResult.new(dependencies: dependencies)
37
53
  end
54
+
55
+ def self.parse_cpanfile(file_contents, options: {})
56
+ filename = options.fetch(:filename, nil)
57
+ dependencies = []
58
+ current_phase = "runtime"
59
+ current_feature = nil
60
+
61
+ file_contents.each_line do |line|
62
+ line = line.strip
63
+
64
+ # Track phase changes: on 'test' => sub {
65
+ if line =~ /\bon\s+['"](\w+)['"]\s*=>/
66
+ current_phase = $1
67
+ next
68
+ end
69
+
70
+ # Track feature blocks: feature 'name', 'desc' => sub {
71
+ if line =~ /\bfeature\s+['"]([\w-]+)['"]/
72
+ current_feature = $1
73
+ next
74
+ end
75
+
76
+ # End of block - reset to defaults
77
+ if line =~ /^\s*\};\s*$/
78
+ current_phase = "runtime"
79
+ current_feature = nil
80
+ next
81
+ end
82
+
83
+ # Parse dependency declarations
84
+ # requires 'Module::Name', 'version';
85
+ # requires 'Module::Name';
86
+ # recommends 'Module::Name', 'version';
87
+ if line =~ /\b(requires|recommends|suggests|conflicts)\s+['"]([^'"]+)['"](?:\s*,\s*['"]?([^'";]+)['"]?)?/
88
+ dep_type = $1
89
+ name = $2
90
+ version = $3&.strip || "*"
91
+
92
+ # Map cpanfile phases to our types
93
+ type = case current_phase
94
+ when "test" then "test"
95
+ when "develop" then "develop"
96
+ when "build" then "build"
97
+ when "configure" then "build"
98
+ else dep_type == "requires" ? "runtime" : dep_type
99
+ end
100
+
101
+ dependencies << Dependency.new(
102
+ name: name,
103
+ requirement: version,
104
+ type: type,
105
+ platform: "cpan",
106
+ source: filename
107
+ )
108
+ end
109
+ end
110
+
111
+ ParserResult.new(dependencies: dependencies)
112
+ end
113
+
114
+ def self.parse_cpanfile_snapshot(file_contents, options: {})
115
+ filename = options.fetch(:filename, nil)
116
+ dependencies = []
117
+
118
+ file_contents.each_line do |line|
119
+ # Match distribution header: Module-Name-1.23
120
+ if (match = line.match(/^ (\S+)-v?([\d._]+)$/))
121
+ dist_name = match[1].gsub("-", "::")
122
+ version = match[2]
123
+ dependencies << Dependency.new(
124
+ name: dist_name,
125
+ requirement: version,
126
+ type: "runtime",
127
+ platform: "cpan",
128
+ source: filename
129
+ )
130
+ end
131
+ end
132
+
133
+ ParserResult.new(dependencies: dependencies)
134
+ end
135
+
136
+ # Parse Makefile.PL (ExtUtils::MakeMaker format)
137
+ # Looks for PREREQ_PM, BUILD_REQUIRES, TEST_REQUIRES, CONFIGURE_REQUIRES
138
+ def self.parse_makefile_pl(file_contents, options: {})
139
+ filename = options.fetch(:filename, nil)
140
+ dependencies = []
141
+
142
+ # Map of hash key names to dependency types
143
+ type_mapping = {
144
+ "PREREQ_PM" => "runtime",
145
+ "BUILD_REQUIRES" => "build",
146
+ "TEST_REQUIRES" => "test",
147
+ "CONFIGURE_REQUIRES" => "build",
148
+ }
149
+
150
+ type_mapping.each do |key, type|
151
+ deps = extract_perl_hash(file_contents, key)
152
+ deps.each do |name, version|
153
+ dependencies << Dependency.new(
154
+ name: name,
155
+ requirement: version,
156
+ type: type,
157
+ platform: "cpan",
158
+ source: filename
159
+ )
160
+ end
161
+ end
162
+
163
+ ParserResult.new(dependencies: dependencies)
164
+ end
165
+
166
+ # Parse Build.PL (Module::Build format)
167
+ # Looks for requires, build_requires, test_requires, configure_requires
168
+ def self.parse_build_pl(file_contents, options: {})
169
+ filename = options.fetch(:filename, nil)
170
+ dependencies = []
171
+
172
+ # Map of hash key names to dependency types
173
+ type_mapping = {
174
+ "requires" => "runtime",
175
+ "build_requires" => "build",
176
+ "test_requires" => "test",
177
+ "configure_requires" => "build",
178
+ "recommends" => "runtime",
179
+ }
180
+
181
+ type_mapping.each do |key, type|
182
+ deps = extract_perl_hash(file_contents, key)
183
+ deps.each do |name, version|
184
+ dependencies << Dependency.new(
185
+ name: name,
186
+ requirement: version,
187
+ type: type,
188
+ platform: "cpan",
189
+ source: filename
190
+ )
191
+ end
192
+ end
193
+
194
+ ParserResult.new(dependencies: dependencies)
195
+ end
196
+
197
+ # Extract a Perl hash from source code
198
+ # Handles patterns like: KEY => { 'Module' => '1.0', ... }
199
+ def self.extract_perl_hash(content, key)
200
+ deps = {}
201
+
202
+ # Match the hash assignment: KEY => { ... }
203
+ # Use word boundary to avoid matching configure_requires when looking for requires
204
+ pattern = /(?:^|[^\w])#{Regexp.escape(key)}\s*=>\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/m
205
+
206
+ if (match = content.match(pattern))
207
+ hash_content = match[1]
208
+
209
+ # Extract 'Module::Name' => 'version' or 'Module::Name' => version patterns
210
+ hash_content.scan(/['"]([^'"]+)['"]\s*=>\s*['"]?([^'",}\s]+)['"]?/) do |name, version|
211
+ # Skip perl version requirements and non-module entries
212
+ next if name == "perl"
213
+
214
+ # Normalize version: 0 means any version
215
+ version = "*" if version == "0"
216
+ deps[name] = version
217
+ end
218
+ end
219
+
220
+ deps
221
+ end
38
222
  end
39
223
  end
40
224
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Bibliothecary
4
6
  module Parsers
5
7
  class CRAN
@@ -13,6 +15,10 @@ module Bibliothecary
13
15
  kind: "manifest",
14
16
  parser: :parse_description,
15
17
  },
18
+ match_filename("renv.lock") => {
19
+ kind: "lockfile",
20
+ parser: :parse_renv_lock,
21
+ },
16
22
  }
17
23
  end
18
24
 
@@ -67,6 +73,29 @@ module Bibliothecary
67
73
  )
68
74
  end.compact
69
75
  end
76
+
77
+ def self.parse_renv_lock(file_contents, options: {})
78
+ source = options.fetch(:filename, nil)
79
+ manifest = JSON.parse(file_contents)
80
+ packages = manifest.fetch("Packages", {})
81
+
82
+ dependencies = packages.map do |_key, pkg|
83
+ # Only include packages from CRAN repository
84
+ # Skip local packages and packages from other sources like Bioconductor
85
+ repository = pkg["Repository"]
86
+ next unless repository == "CRAN"
87
+
88
+ Dependency.new(
89
+ name: pkg["Package"],
90
+ requirement: pkg["Version"],
91
+ type: "runtime",
92
+ source: source,
93
+ platform: platform_name
94
+ )
95
+ end.compact
96
+
97
+ ParserResult.new(dependencies: dependencies)
98
+ end
70
99
  end
71
100
  end
72
101
  end
@@ -12,6 +12,9 @@ module Bibliothecary
12
12
  # Matches build-tool-depends format: package:tool == version
13
13
  BUILD_TOOL_REGEXP = /^\s*([a-zA-Z][a-zA-Z0-9-]*):[a-zA-Z][a-zA-Z0-9-]*\s*((?:[<>=!]+\s*[\d.*]+(?:\s*&&\s*[<>=!]+\s*[\d.*]+)*)?)/
14
14
 
15
+ # Matches stack.yaml.lock hackage entries like: hackage: fuzzyset-0.2.4@sha256:...
16
+ STACK_LOCK_REGEXP = /hackage:\s*([a-zA-Z0-9-]+)-([0-9.]+)@/
17
+
15
18
  def self.mapping
16
19
  {
17
20
  match_extension(".cabal") => {
@@ -22,6 +25,10 @@ module Bibliothecary
22
25
  kind: "lockfile",
23
26
  parser: :parse_cabal_config,
24
27
  },
28
+ match_filename("stack.yaml.lock") => {
29
+ kind: "lockfile",
30
+ parser: :parse_stack_yaml_lock,
31
+ },
25
32
  }
26
33
  end
27
34
 
@@ -169,6 +176,26 @@ module Bibliothecary
169
176
 
170
177
  ParserResult.new(dependencies: deps)
171
178
  end
179
+
180
+ def self.parse_stack_yaml_lock(file_contents, options: {})
181
+ source = options.fetch(:filename, "stack.yaml.lock")
182
+ deps = []
183
+
184
+ file_contents.each_line do |line|
185
+ match = line.match(STACK_LOCK_REGEXP)
186
+ next unless match
187
+
188
+ deps << Dependency.new(
189
+ platform: platform_name,
190
+ name: match[1],
191
+ requirement: match[2],
192
+ type: "runtime",
193
+ source: source
194
+ )
195
+ end
196
+
197
+ ParserResult.new(dependencies: deps)
198
+ end
172
199
  end
173
200
  end
174
201
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bibliothecary
4
+ module Parsers
5
+ class LuaRocks
6
+ include Bibliothecary::Analyser
7
+
8
+ def self.mapping
9
+ {
10
+ match_extension(".rockspec") => {
11
+ kind: "manifest",
12
+ parser: :parse_rockspec,
13
+ },
14
+ }
15
+ end
16
+
17
+ def self.parse_rockspec(file_contents, options: {})
18
+ source = options.fetch(:filename, nil)
19
+ deps = []
20
+
21
+ # Find dependencies table in Lua format
22
+ # dependencies = { "lua >= 5.1", "package ~> 1.0" }
23
+ if file_contents =~ /dependencies\s*=\s*\{([^}]*)\}/m
24
+ deps_block = Regexp.last_match(1)
25
+
26
+ # Extract quoted strings from the dependencies table
27
+ deps_block.scan(/"([^"]+)"/).flatten.each do |spec|
28
+ spec = spec.strip
29
+ next if spec.empty?
30
+
31
+ # Parse "packagename" or "packagename >= version" or "packagename ~> version"
32
+ # Format: name [operator version]
33
+ if spec =~ /^([a-zA-Z0-9_-]+)\s*(.*)$/
34
+ name = Regexp.last_match(1)
35
+ requirement = Regexp.last_match(2).strip
36
+ requirement = "*" if requirement.empty?
37
+
38
+ deps << Dependency.new(
39
+ name: name,
40
+ requirement: requirement,
41
+ type: "runtime",
42
+ source: source,
43
+ platform: platform_name
44
+ )
45
+ end
46
+ end
47
+ end
48
+
49
+ ParserResult.new(dependencies: deps)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -130,6 +130,18 @@ module Bibliothecary
130
130
  kind: "lockfile",
131
131
  parser: :parse_maven_tree_dot,
132
132
  },
133
+ # gradle.lockfile is the output of Gradle dependency locking:
134
+ # https://docs.gradle.org/current/userguide/dependency_locking.html
135
+ match_filename("gradle.lockfile", case_insensitive: true) => {
136
+ kind: "lockfile",
137
+ parser: :parse_gradle_lockfile,
138
+ },
139
+ # gradle/verification-metadata.xml is used by Gradle for dependency verification
140
+ # https://docs.gradle.org/current/userguide/dependency_verification.html
141
+ match_filename("verification-metadata.xml", case_insensitive: true) => {
142
+ kind: "lockfile",
143
+ parser: :parse_gradle_verification_metadata,
144
+ },
133
145
  }
134
146
  end
135
147
 
@@ -413,6 +425,68 @@ module Bibliothecary
413
425
  )
414
426
  end
415
427
 
428
+ def self.parse_gradle_lockfile(file_contents, options: {})
429
+ source = options.fetch(:filename, nil)
430
+ deps = []
431
+
432
+ file_contents.each_line do |line|
433
+ line = line.strip
434
+ # Skip comments and empty lines
435
+ next if line.empty? || line.start_with?("#")
436
+
437
+ # Format: group:artifact:version=configurations
438
+ # Split on = first to separate the GAV from configurations
439
+ gav_part = line.split("=").first
440
+ next unless gav_part
441
+
442
+ parts = gav_part.split(":")
443
+ # Must have exactly 3 parts: group, artifact, version
444
+ next unless parts.length == 3
445
+
446
+ group, artifact, version = parts
447
+ # Skip empty entries (like "empty=")
448
+ next if version.nil? || version.empty?
449
+
450
+ deps << Dependency.new(
451
+ name: "#{group}:#{artifact}",
452
+ requirement: version,
453
+ type: "runtime",
454
+ source: source,
455
+ platform: platform_name
456
+ )
457
+ end
458
+
459
+ ParserResult.new(dependencies: deps)
460
+ end
461
+
462
+ def self.parse_gradle_verification_metadata(file_contents, options: {})
463
+ source = options.fetch(:filename, nil)
464
+ deps = []
465
+
466
+ doc = Ox.parse(file_contents)
467
+ components = doc.locate("verification-metadata/components/component")
468
+
469
+ components.each do |component|
470
+ attrs = component.attributes
471
+ group = attrs[:group]
472
+ name = attrs[:name]
473
+ version = attrs[:version]
474
+
475
+ next if group.nil? || name.nil? || version.nil?
476
+ next if group.empty? || name.empty? || version.empty?
477
+
478
+ deps << Dependency.new(
479
+ name: "#{group}:#{name}",
480
+ requirement: version,
481
+ type: "runtime",
482
+ source: source,
483
+ platform: platform_name
484
+ )
485
+ end
486
+
487
+ ParserResult.new(dependencies: deps)
488
+ end
489
+
416
490
  def self.parse_resolved_dep_line(line, options: {})
417
491
  # filter out anything that doesn't look like a
418
492
  # resolved dep line
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bibliothecary
4
+ module Parsers
5
+ class Nimble
6
+ include Bibliothecary::Analyser
7
+
8
+ # Matches requires statements like: requires "nim >= 1.0.0", "chronos >= 3.0.0"
9
+ # or: requires "packagename"
10
+ REQUIRES_REGEXP = /^\s*requires\s+"([^"]+)"/
11
+
12
+ def self.mapping
13
+ {
14
+ match_extension(".nimble") => {
15
+ kind: "manifest",
16
+ parser: :parse_nimble,
17
+ },
18
+ }
19
+ end
20
+
21
+ def self.parse_nimble(file_contents, options: {})
22
+ source = options.fetch(:filename, nil)
23
+ deps = []
24
+
25
+ file_contents.each_line do |line|
26
+ next unless line.strip.start_with?("requires")
27
+
28
+ # Extract all quoted strings from requires lines
29
+ # handles: requires "pkg1", "pkg2 >= 1.0"
30
+ line.scan(/"([^"]+)"/).flatten.each do |spec|
31
+ spec = spec.strip
32
+ next if spec.empty?
33
+
34
+ # Parse "packagename" or "packagename >= version" or "packagename == version"
35
+ if spec =~ /^([a-zA-Z0-9_]+)\s*(.*)$/
36
+ name = Regexp.last_match(1)
37
+ requirement = Regexp.last_match(2).strip
38
+ requirement = "*" if requirement.empty?
39
+
40
+ deps << Dependency.new(
41
+ name: name,
42
+ requirement: requirement,
43
+ type: "runtime",
44
+ source: source,
45
+ platform: platform_name
46
+ )
47
+ end
48
+ end
49
+ end
50
+
51
+ ParserResult.new(dependencies: deps)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -42,6 +42,11 @@ module Bibliothecary
42
42
  kind: "lockfile",
43
43
  parser: :parse_project_assets_json,
44
44
  },
45
+ # .deps.json files end with .deps.json (e.g. myapp.deps.json)
46
+ match_extension(".deps.json") => {
47
+ kind: "lockfile",
48
+ parser: :parse_deps_json,
49
+ },
45
50
  }
46
51
  end
47
52
 
@@ -246,6 +251,33 @@ module Bibliothecary
246
251
  end
247
252
  ParserResult.new(dependencies: [])
248
253
  end
254
+
255
+ def self.parse_deps_json(file_contents, options: {})
256
+ manifest = JSON.parse(file_contents)
257
+ libraries = manifest.fetch("libraries", {})
258
+
259
+ dependencies = libraries.map do |name_version, details|
260
+ # Skip project-type entries (these are the root/main package)
261
+ next if details["type"] == "project"
262
+
263
+ # Split name/version format
264
+ parts = name_version.split("/")
265
+ next unless parts.length == 2
266
+
267
+ name, version = parts
268
+ next if name.nil? || name.empty? || version.nil? || version.empty?
269
+
270
+ Dependency.new(
271
+ name: name,
272
+ requirement: version,
273
+ type: "runtime",
274
+ source: options.fetch(:filename, nil),
275
+ platform: platform_name
276
+ )
277
+ end.compact
278
+
279
+ ParserResult.new(dependencies: dependencies)
280
+ end
249
281
  end
250
282
  end
251
283
  end
@@ -85,6 +85,10 @@ module Bibliothecary
85
85
  kind: "lockfile",
86
86
  parser: :parser_pylock,
87
87
  },
88
+ match_filename("pdm.lock") => {
89
+ kind: "lockfile",
90
+ parser: :parse_pdm_lock,
91
+ },
88
92
  }
89
93
  end
90
94
 
@@ -129,6 +133,34 @@ module Bibliothecary
129
133
  ParserResult.new(dependencies: dependencies)
130
134
  end
131
135
 
136
+ def self.parse_pdm_lock(file_contents, options: {})
137
+ source = options.fetch(:filename, nil)
138
+ dependencies = []
139
+ # Split into [[package]] blocks and extract fields from each
140
+ file_contents.split(/\[\[package\]\]/).drop(1).each do |block|
141
+ name = block[/^name\s*=\s*"([^"]+)"/m, 1]
142
+ version = block[/^version\s*=\s*"([^"]+)"/m, 1]
143
+ # PDM stores groups as an array, e.g. groups = ["default"] or groups = ["dev"]
144
+ groups_match = block[/^groups\s*=\s*\[([^\]]+)\]/m, 1]
145
+ groups = groups_match ? groups_match.scan(/"([^"]+)"/).flatten : ["default"]
146
+
147
+ type = if groups.include?("dev")
148
+ "develop"
149
+ else
150
+ "runtime"
151
+ end
152
+
153
+ dependencies << Dependency.new(
154
+ platform: platform_name,
155
+ name: name,
156
+ requirement: version,
157
+ type: type,
158
+ source: source
159
+ )
160
+ end
161
+ ParserResult.new(dependencies: dependencies)
162
+ end
163
+
132
164
  def self.parse_pipfile(file_contents, options: {})
133
165
  manifest = Tomlrb.parse(file_contents)
134
166
  dependencies = map_dependencies(manifest["packages"], "runtime", options.fetch(:filename, nil)) +
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bibliothecary
4
- VERSION = "15.0.1"
4
+ VERSION = "15.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecosystems-bibliothecary
3
3
  version: !ruby/object:Gem::Version
4
- version: 15.0.1
4
+ version: 15.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
@@ -138,9 +138,11 @@ files:
138
138
  - lib/bibliothecary/parsers/hex.rb
139
139
  - lib/bibliothecary/parsers/homebrew.rb
140
140
  - lib/bibliothecary/parsers/julia.rb
141
+ - lib/bibliothecary/parsers/luarocks.rb
141
142
  - lib/bibliothecary/parsers/maven.rb
142
143
  - lib/bibliothecary/parsers/meteor.rb
143
144
  - lib/bibliothecary/parsers/mlflow.rb
145
+ - lib/bibliothecary/parsers/nimble.rb
144
146
  - lib/bibliothecary/parsers/npm.rb
145
147
  - lib/bibliothecary/parsers/nuget.rb
146
148
  - lib/bibliothecary/parsers/ollama.rb