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
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Bibliothecary
6
+ module Parsers
7
+ class Deno
8
+ include Bibliothecary::Analyser
9
+
10
+ def self.file_patterns
11
+ ["deno.json", "deno.jsonc", "deno.lock"]
12
+ end
13
+
14
+ def self.mapping
15
+ {
16
+ match_filename("deno.json") => {
17
+ kind: "manifest",
18
+ parser: :parse_manifest,
19
+ },
20
+ match_filename("deno.jsonc") => {
21
+ kind: "manifest",
22
+ parser: :parse_manifest,
23
+ },
24
+ match_filename("deno.lock") => {
25
+ kind: "lockfile",
26
+ parser: :parse_lockfile,
27
+ },
28
+ }
29
+ end
30
+
31
+ def self.parse_manifest(file_contents, options: {})
32
+ manifest = JSON.parse(file_contents)
33
+ source = options.fetch(:filename, nil)
34
+
35
+ dependencies = manifest.fetch("imports", {}).map do |alias_name, specifier|
36
+ name, requirement = parse_specifier(specifier)
37
+ next unless name
38
+
39
+ Dependency.new(
40
+ name: name,
41
+ requirement: requirement,
42
+ type: "runtime",
43
+ source: source,
44
+ platform: platform_name
45
+ )
46
+ end.compact
47
+
48
+ ParserResult.new(dependencies: dependencies)
49
+ end
50
+
51
+ def self.parse_lockfile(file_contents, options: {})
52
+ manifest = JSON.parse(file_contents)
53
+ source = options.fetch(:filename, nil)
54
+
55
+ dependencies = manifest.fetch("specifiers", {}).map do |specifier, resolved_version|
56
+ name, _requirement = parse_specifier(specifier)
57
+ next unless name
58
+
59
+ Dependency.new(
60
+ name: name,
61
+ requirement: resolved_version,
62
+ type: "runtime",
63
+ source: source,
64
+ platform: platform_name
65
+ )
66
+ end.compact
67
+
68
+ ParserResult.new(dependencies: dependencies)
69
+ end
70
+
71
+ # Parses specifiers like:
72
+ # "npm:chalk@1" => ["chalk", "1"]
73
+ # "npm:chalk" => ["chalk", "*"]
74
+ # "jsr:@std/path@^1" => ["@std/path", "^1"]
75
+ # "jsr:@std/path" => ["@std/path", "*"]
76
+ def self.parse_specifier(specifier)
77
+ return nil unless specifier.start_with?("npm:", "jsr:")
78
+
79
+ # Remove the protocol prefix
80
+ without_protocol = specifier.sub(/^(npm|jsr):/, "")
81
+
82
+ # Handle scoped packages (@scope/name@version)
83
+ if without_protocol.start_with?("@")
84
+ # Split on @ but keep the first @ for the scope
85
+ parts = without_protocol[1..].split("@", 2)
86
+ name = "@#{parts[0]}"
87
+ requirement = parts[1] || "*"
88
+ else
89
+ # Regular package (name@version)
90
+ name, requirement = without_protocol.split("@", 2)
91
+ requirement ||= "*"
92
+ end
93
+
94
+ [name, requirement]
95
+ end
96
+ end
97
+ end
98
+ end
@@ -8,6 +8,10 @@ module Bibliothecary
8
8
 
9
9
  DOCKER_COMPOSE_REGEXP = /docker-compose[a-zA-Z0-9\-_\.]*\.yml$/
10
10
 
11
+ def self.file_patterns
12
+ ["docker-compose*.yml", "Dockerfile"]
13
+ end
14
+
11
15
  def self.mapping
12
16
  {
13
17
  lambda { |p| DOCKER_COMPOSE_REGEXP.match(p) } => {
@@ -9,6 +9,10 @@ module Bibliothecary
9
9
 
10
10
  SDL_DEPENDENCY_REGEXP = /^dependency\s+"([^"]+)"(?:\s+version="([^"]+)")?/
11
11
 
12
+ def self.file_patterns
13
+ ["dub.json", "dub.sdl"]
14
+ end
15
+
12
16
  def self.mapping
13
17
  {
14
18
  match_filename("dub.json") => {
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class DVC
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["dvc.yaml"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("dvc.yaml") => {
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
  class Elm
8
8
  include Bibliothecary::Analyser
9
9
 
10
+ def self.file_patterns
11
+ ["elm-package.json", "elm_dependencies.json", "elm-stuff/exact-dependencies.json"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filenames("elm-package.json", "elm_dependencies.json") => {
@@ -18,6 +18,10 @@ module Bibliothecary
18
18
  GOMOD_MULTILINE_END_REGEXP = /^\)/
19
19
  GOSUM_REGEXP = /^(.+)\s+(.+)\s+(.+)$/
20
20
 
21
+ def self.file_patterns
22
+ ["go.mod", "go.sum", "glide.yaml", "glide.lock", "Godeps/Godeps.json", "Godeps", "vendor/manifest", "vendor/vendor.json", "Gopkg.toml", "Gopkg.lock", "go-resolved-dependencies.json"]
23
+ end
24
+
21
25
  def self.mapping
22
26
  {
23
27
  # Go Modules (recommended)
@@ -15,6 +15,10 @@ module Bibliothecary
15
15
  # Matches stack.yaml.lock hackage entries like: hackage: fuzzyset-0.2.4@sha256:...
16
16
  STACK_LOCK_REGEXP = /hackage:\s*([a-zA-Z0-9-]+)-([0-9.]+)@/
17
17
 
18
+ def self.file_patterns
19
+ ["*.cabal", "*cabal.config", "stack.yaml.lock", "cabal.project.freeze"]
20
+ end
21
+
18
22
  def self.mapping
19
23
  {
20
24
  match_extension(".cabal") => {
@@ -29,6 +33,10 @@ module Bibliothecary
29
33
  kind: "lockfile",
30
34
  parser: :parse_stack_yaml_lock,
31
35
  },
36
+ match_filename("cabal.project.freeze") => {
37
+ kind: "lockfile",
38
+ parser: :parse_cabal_project_freeze,
39
+ },
32
40
  }
33
41
  end
34
42
 
@@ -196,6 +204,53 @@ module Bibliothecary
196
204
 
197
205
  ParserResult.new(dependencies: deps)
198
206
  end
207
+
208
+ def self.parse_cabal_project_freeze(file_contents, options: {})
209
+ source = options.fetch(:filename, "cabal.project.freeze")
210
+ deps = []
211
+
212
+ # Parse constraints field which can span multiple lines
213
+ # Format: constraints: any.pkg ==version, any.pkg2 ==version2, ...
214
+ # Also handles flag constraints like: any.pkg +flag -flag2
215
+ constraints = nil
216
+ file_contents.each_line do |line|
217
+ if line =~ /^constraints:\s*(.*)/i
218
+ constraints = $1.strip
219
+ elsif line =~ /^\s+(.*)/ && constraints
220
+ constraints += " " + $1.strip
221
+ elsif line =~ /^[a-z]/i && constraints
222
+ break
223
+ end
224
+ end
225
+
226
+ return ParserResult.new(dependencies: []) unless constraints
227
+
228
+ constraints.split(",").each do |dep_str|
229
+ dep_str = dep_str.strip
230
+ next if dep_str.empty?
231
+
232
+ # Format: any.package ==version or any.package +flag -flag
233
+ # Skip flag-only entries (no version constraint)
234
+ next unless dep_str.include?("==")
235
+
236
+ # Remove "any." prefix and parse
237
+ dep_str = dep_str.sub(/^any\./, "")
238
+
239
+ # Extract name and version: "package ==version" or "package ==version +flag"
240
+ match = dep_str.match(/^([a-zA-Z][a-zA-Z0-9-]*)\s*==\s*([\d.]+)/)
241
+ next unless match
242
+
243
+ deps << Dependency.new(
244
+ platform: platform_name,
245
+ name: match[1],
246
+ requirement: match[2],
247
+ type: "runtime",
248
+ source: source
249
+ )
250
+ end
251
+
252
+ ParserResult.new(dependencies: deps)
253
+ end
199
254
  end
200
255
  end
201
256
  end
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
  class Haxelib
8
8
  include Bibliothecary::Analyser
9
9
 
10
+ def self.file_patterns
11
+ ["haxelib.json"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filename("haxelib.json") => {
@@ -10,6 +10,13 @@ module Bibliothecary
10
10
  HEX_LOCK_REGEXP = /"([^"]+)":\s*\{:hex,\s*:[^,]+,\s*"([^"]+)"/
11
11
  GIT_LOCK_REGEXP = /"([^"]+)":\s*\{:git,\s*"([^"]+)",\s*"([^"]+)"/
12
12
 
13
+ # Matches rebar.lock entries: {<<"name">>,{pkg,<<"name">>,<<"version">>},N}
14
+ REBAR_LOCK_REGEXP = /\{<<"([^"]+)">>,\{pkg,<<"[^"]+">>,<<"([^"]+)">>},\d+\}/
15
+
16
+ def self.file_patterns
17
+ ["mix.exs", "mix.lock", "gleam.toml", "manifest.toml", "rebar.lock"]
18
+ end
19
+
13
20
  def self.mapping
14
21
  {
15
22
  match_filename("mix.exs") => {
@@ -20,9 +27,26 @@ module Bibliothecary
20
27
  kind: "lockfile",
21
28
  parser: :parse_mix_lock,
22
29
  },
30
+ match_filename("gleam.toml") => {
31
+ kind: "manifest",
32
+ parser: :parse_gleam_toml,
33
+ },
34
+ match_filename("manifest.toml") => {
35
+ kind: "lockfile",
36
+ parser: :parse_gleam_manifest,
37
+ content_matcher: :gleam_manifest?,
38
+ },
39
+ match_filename("rebar.lock") => {
40
+ kind: "lockfile",
41
+ parser: :parse_rebar_lock,
42
+ },
23
43
  }
24
44
  end
25
45
 
46
+ def self.gleam_manifest?(file_contents)
47
+ file_contents.include?("# This file was generated by Gleam")
48
+ end
49
+
26
50
 
27
51
  def self.parse_mix(file_contents, options: {})
28
52
  source = options.fetch(:filename, "mix.exs")
@@ -74,6 +98,71 @@ module Bibliothecary
74
98
 
75
99
  ParserResult.new(dependencies: deps)
76
100
  end
101
+
102
+ def self.parse_gleam_toml(file_contents, options: {})
103
+ source = options.fetch(:filename, "gleam.toml")
104
+ manifest = Tomlrb.parse(file_contents)
105
+ deps = []
106
+
107
+ manifest.fetch("dependencies", {}).each do |name, requirement|
108
+ deps << Dependency.new(
109
+ platform: platform_name,
110
+ name: name,
111
+ requirement: requirement,
112
+ type: "runtime",
113
+ source: source
114
+ )
115
+ end
116
+
117
+ manifest.fetch("dev-dependencies", {}).each do |name, requirement|
118
+ deps << Dependency.new(
119
+ platform: platform_name,
120
+ name: name,
121
+ requirement: requirement,
122
+ type: "development",
123
+ source: source
124
+ )
125
+ end
126
+
127
+ ParserResult.new(dependencies: deps)
128
+ end
129
+
130
+ def self.parse_gleam_manifest(file_contents, options: {})
131
+ source = options.fetch(:filename, "manifest.toml")
132
+ manifest = Tomlrb.parse(file_contents)
133
+ deps = []
134
+
135
+ manifest.fetch("packages", []).each do |pkg|
136
+ next unless pkg["source"] == "hex"
137
+
138
+ deps << Dependency.new(
139
+ platform: platform_name,
140
+ name: pkg["name"],
141
+ requirement: pkg["version"],
142
+ type: "runtime",
143
+ source: source
144
+ )
145
+ end
146
+
147
+ ParserResult.new(dependencies: deps)
148
+ end
149
+
150
+ def self.parse_rebar_lock(file_contents, options: {})
151
+ source = options.fetch(:filename, "rebar.lock")
152
+ deps = []
153
+
154
+ file_contents.scan(REBAR_LOCK_REGEXP) do |name, version|
155
+ deps << Dependency.new(
156
+ platform: platform_name,
157
+ name: name,
158
+ requirement: version,
159
+ type: "runtime",
160
+ source: source
161
+ )
162
+ end
163
+
164
+ ParserResult.new(dependencies: deps)
165
+ end
77
166
  end
78
167
  end
79
168
  end
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
 
6
6
  HOMEBREW_REGEXP = /^brew\s(.+),?\s?/
7
7
 
8
+ def self.file_patterns
9
+ ["Brewfile", "Brewfile.lock.json"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("Brewfile", case_insensitive: true) => {
@@ -5,12 +5,24 @@ module Bibliothecary
5
5
  class Julia
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["REQUIRE", "Project.toml", "Manifest.toml"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("REQUIRE", case_insensitive: true) => {
11
15
  kind: "manifest",
12
16
  parser: :parse_require,
13
17
  },
18
+ match_filename("Project.toml") => {
19
+ kind: "manifest",
20
+ parser: :parse_project_toml,
21
+ },
22
+ match_filename("Manifest.toml") => {
23
+ kind: "lockfile",
24
+ parser: :parse_manifest_toml,
25
+ },
14
26
  }
15
27
  end
16
28
 
@@ -41,6 +53,49 @@ module Bibliothecary
41
53
  end
42
54
  ParserResult.new(dependencies: deps)
43
55
  end
56
+
57
+ def self.parse_project_toml(file_contents, options: {})
58
+ source = options.fetch(:filename, "Project.toml")
59
+ manifest = Tomlrb.parse(file_contents)
60
+ deps = []
61
+
62
+ manifest.fetch("deps", {}).each do |name, _uuid|
63
+ deps << Dependency.new(
64
+ name: name,
65
+ requirement: "*",
66
+ type: "runtime",
67
+ source: source,
68
+ platform: platform_name
69
+ )
70
+ end
71
+
72
+ ParserResult.new(dependencies: deps)
73
+ end
74
+
75
+ def self.parse_manifest_toml(file_contents, options: {})
76
+ source = options.fetch(:filename, "Manifest.toml")
77
+ manifest = Tomlrb.parse(file_contents)
78
+ deps = []
79
+
80
+ manifest.fetch("deps", {}).each do |name, entries|
81
+ # entries is an array of package entries (usually just one)
82
+ entry = entries.is_a?(Array) ? entries.first : entries
83
+ next unless entry.is_a?(Hash)
84
+
85
+ version = entry["version"]
86
+ next unless version
87
+
88
+ deps << Dependency.new(
89
+ name: name,
90
+ requirement: version,
91
+ type: "runtime",
92
+ source: source,
93
+ platform: platform_name
94
+ )
95
+ end
96
+
97
+ ParserResult.new(dependencies: deps)
98
+ end
44
99
  end
45
100
  end
46
101
  end
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class LuaRocks
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["*.rockspec"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_extension(".rockspec") => {
@@ -62,6 +62,10 @@ module Bibliothecary
62
62
  # e.g. "[info] "
63
63
  SBT_IGNORE_REGEXP = /^\[info\]\s*$/
64
64
 
65
+ def self.file_patterns
66
+ ["ivy.xml", "pom.xml", "build.gradle", "build.gradle.kts", "gradle-dependencies-q.txt", "maven-resolved-dependencies.txt", "sbt-update-full.txt", "maven-dependency-tree.txt", "maven-dependency-tree.dot", "gradle.lockfile", "verification-metadata.xml"]
67
+ end
68
+
65
69
  # Copied from the "strings-ansi" gem, because it seems abandoned: https://github.com/piotrmurach/strings-ansi/pull/2
66
70
  # From: https://github.com/piotrmurach/strings-ansi/blob/35d0c9430cf0a8022dc12bdab005bce296cb9f00/lib/strings/ansi.rb#L14-L29
67
71
  # License: MIT
@@ -7,6 +7,10 @@ module Bibliothecary
7
7
  class Meteor
8
8
  include Bibliothecary::Analyser
9
9
 
10
+ def self.file_patterns
11
+ ["versions.json"]
12
+ end
13
+
10
14
  def self.mapping
11
15
  {
12
16
  match_filename("versions.json") => {
@@ -5,6 +5,10 @@ module Bibliothecary
5
5
  class MLflow
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ def self.file_patterns
9
+ ["MLmodel"]
10
+ end
11
+
8
12
  def self.mapping
9
13
  {
10
14
  match_filename("MLmodel") => {
@@ -9,6 +9,10 @@ module Bibliothecary
9
9
  # or: requires "packagename"
10
10
  REQUIRES_REGEXP = /^\s*requires\s+"([^"]+)"/
11
11
 
12
+ def self.file_patterns
13
+ ["*.nimble"]
14
+ end
15
+
12
16
  def self.mapping
13
17
  {
14
18
  match_extension(".nimble") => {