ecosystems-bibliothecary 15.1.1 → 15.3.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +189 -124
  4. data/lib/bibliothecary/analyser.rb +4 -0
  5. data/lib/bibliothecary/dependency.rb +6 -1
  6. data/lib/bibliothecary/parsers/actions.rb +4 -0
  7. data/lib/bibliothecary/parsers/alpm.rb +89 -0
  8. data/lib/bibliothecary/parsers/apk.rb +91 -0
  9. data/lib/bibliothecary/parsers/bentoml.rb +5 -1
  10. data/lib/bibliothecary/parsers/bower.rb +5 -0
  11. data/lib/bibliothecary/parsers/cargo.rb +7 -1
  12. data/lib/bibliothecary/parsers/carthage.rb +4 -0
  13. data/lib/bibliothecary/parsers/clojars.rb +5 -0
  14. data/lib/bibliothecary/parsers/cocoapods.rb +33 -1
  15. data/lib/bibliothecary/parsers/cog.rb +5 -1
  16. data/lib/bibliothecary/parsers/conan.rb +4 -0
  17. data/lib/bibliothecary/parsers/conda.rb +6 -0
  18. data/lib/bibliothecary/parsers/cpan.rb +4 -0
  19. data/lib/bibliothecary/parsers/cran.rb +4 -0
  20. data/lib/bibliothecary/parsers/deb.rb +132 -0
  21. data/lib/bibliothecary/parsers/deno.rb +112 -0
  22. data/lib/bibliothecary/parsers/docker.rb +4 -0
  23. data/lib/bibliothecary/parsers/dub.rb +6 -0
  24. data/lib/bibliothecary/parsers/dvc.rb +5 -1
  25. data/lib/bibliothecary/parsers/elm.rb +4 -0
  26. data/lib/bibliothecary/parsers/go.rb +8 -2
  27. data/lib/bibliothecary/parsers/hackage.rb +58 -2
  28. data/lib/bibliothecary/parsers/haxelib.rb +5 -0
  29. data/lib/bibliothecary/parsers/hex.rb +109 -5
  30. data/lib/bibliothecary/parsers/homebrew.rb +4 -0
  31. data/lib/bibliothecary/parsers/julia.rb +55 -0
  32. data/lib/bibliothecary/parsers/luarocks.rb +5 -0
  33. data/lib/bibliothecary/parsers/maven.rb +4 -0
  34. data/lib/bibliothecary/parsers/meteor.rb +5 -0
  35. data/lib/bibliothecary/parsers/mlflow.rb +5 -1
  36. data/lib/bibliothecary/parsers/nimble.rb +5 -0
  37. data/lib/bibliothecary/parsers/nix.rb +205 -0
  38. data/lib/bibliothecary/parsers/npm.rb +84 -11
  39. data/lib/bibliothecary/parsers/nuget.rb +4 -0
  40. data/lib/bibliothecary/parsers/ollama.rb +5 -1
  41. data/lib/bibliothecary/parsers/packagist.rb +32 -31
  42. data/lib/bibliothecary/parsers/pub.rb +4 -0
  43. data/lib/bibliothecary/parsers/pypi.rb +25 -2
  44. data/lib/bibliothecary/parsers/rpm.rb +80 -0
  45. data/lib/bibliothecary/parsers/rubygems.rb +38 -4
  46. data/lib/bibliothecary/parsers/shard.rb +4 -0
  47. data/lib/bibliothecary/parsers/swift_pm.rb +4 -0
  48. data/lib/bibliothecary/parsers/vcpkg.rb +4 -0
  49. data/lib/bibliothecary/version.rb +1 -1
  50. data/lib/bibliothecary.rb +7 -0
  51. metadata +7 -1
@@ -5,11 +5,20 @@ module Bibliothecary
5
5
  class Hex
6
6
  include Bibliothecary::Analyser
7
7
 
8
- # Matches mix.lock entries: "name": {:hex, :name, "version", ...
8
+ # Matches mix.lock entries: "name": {:hex, :name, "version", "hash", ...
9
9
  # or "name": {:git, "url", "ref", ...
10
- HEX_LOCK_REGEXP = /"([^"]+)":\s*\{:hex,\s*:[^,]+,\s*"([^"]+)"/
10
+ HEX_LOCK_REGEXP = /"([^"]+)":\s*\{:hex,\s*:[^,]+,\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
+ # Matches rebar.lock pkg_hash entries: {<<"name">>, <<"HASH">>}
16
+ REBAR_PKG_HASH_REGEXP = /\{<<"([^"]+)">>,\s*<<"([A-F0-9]+)">>}/
17
+
18
+ def self.file_patterns
19
+ ["mix.exs", "mix.lock", "gleam.toml", "manifest.toml", "rebar.lock"]
20
+ end
21
+
13
22
  def self.mapping
14
23
  {
15
24
  match_filename("mix.exs") => {
@@ -20,9 +29,26 @@ module Bibliothecary
20
29
  kind: "lockfile",
21
30
  parser: :parse_mix_lock,
22
31
  },
32
+ match_filename("gleam.toml") => {
33
+ kind: "manifest",
34
+ parser: :parse_gleam_toml,
35
+ },
36
+ match_filename("manifest.toml") => {
37
+ kind: "lockfile",
38
+ parser: :parse_gleam_manifest,
39
+ content_matcher: :gleam_manifest?,
40
+ },
41
+ match_filename("rebar.lock") => {
42
+ kind: "lockfile",
43
+ parser: :parse_rebar_lock,
44
+ },
23
45
  }
24
46
  end
25
47
 
48
+ def self.gleam_manifest?(file_contents)
49
+ file_contents.include?("# This file was generated by Gleam")
50
+ end
51
+
26
52
 
27
53
  def self.parse_mix(file_contents, options: {})
28
54
  source = options.fetch(:filename, "mix.exs")
@@ -50,14 +76,15 @@ module Bibliothecary
50
76
  source = options.fetch(:filename, "mix.lock")
51
77
  deps = []
52
78
 
53
- # Match hex packages: "name": {:hex, :name, "version", ...
54
- file_contents.scan(HEX_LOCK_REGEXP) do |name, version|
79
+ # Match hex packages: "name": {:hex, :name, "version", "hash", ...
80
+ file_contents.scan(HEX_LOCK_REGEXP) do |name, version, hash|
55
81
  deps << Dependency.new(
56
82
  platform: platform_name,
57
83
  name: name,
58
84
  requirement: version,
59
85
  type: "runtime",
60
- source: source
86
+ source: source,
87
+ integrity: "sha256=#{hash}"
61
88
  )
62
89
  end
63
90
 
@@ -74,6 +101,83 @@ module Bibliothecary
74
101
 
75
102
  ParserResult.new(dependencies: deps)
76
103
  end
104
+
105
+ def self.parse_gleam_toml(file_contents, options: {})
106
+ source = options.fetch(:filename, "gleam.toml")
107
+ manifest = Tomlrb.parse(file_contents)
108
+ deps = []
109
+
110
+ manifest.fetch("dependencies", {}).each do |name, requirement|
111
+ deps << Dependency.new(
112
+ platform: platform_name,
113
+ name: name,
114
+ requirement: requirement,
115
+ type: "runtime",
116
+ source: source
117
+ )
118
+ end
119
+
120
+ manifest.fetch("dev-dependencies", {}).each do |name, requirement|
121
+ deps << Dependency.new(
122
+ platform: platform_name,
123
+ name: name,
124
+ requirement: requirement,
125
+ type: "development",
126
+ source: source
127
+ )
128
+ end
129
+
130
+ ParserResult.new(dependencies: deps)
131
+ end
132
+
133
+ def self.parse_gleam_manifest(file_contents, options: {})
134
+ source = options.fetch(:filename, "manifest.toml")
135
+ manifest = Tomlrb.parse(file_contents)
136
+ deps = []
137
+
138
+ manifest.fetch("packages", []).each do |pkg|
139
+ next unless pkg["source"] == "hex"
140
+
141
+ checksum = pkg["outer_checksum"]
142
+ deps << Dependency.new(
143
+ platform: platform_name,
144
+ name: pkg["name"],
145
+ requirement: pkg["version"],
146
+ type: "runtime",
147
+ source: source,
148
+ integrity: checksum ? "sha256=#{checksum.downcase}" : nil
149
+ )
150
+ end
151
+
152
+ ParserResult.new(dependencies: deps)
153
+ end
154
+
155
+ def self.parse_rebar_lock(file_contents, options: {})
156
+ source = options.fetch(:filename, "rebar.lock")
157
+ deps = []
158
+
159
+ # Parse pkg_hash section to build lookup table
160
+ pkg_hashes = {}
161
+ pkg_hash_section = file_contents[/\{pkg_hash,\s*\[(.*?)\]\}/m, 1]
162
+ if pkg_hash_section
163
+ pkg_hash_section.scan(REBAR_PKG_HASH_REGEXP) do |name, hash|
164
+ pkg_hashes[name] = "sha256=#{hash.downcase}"
165
+ end
166
+ end
167
+
168
+ file_contents.scan(REBAR_LOCK_REGEXP) do |name, version|
169
+ deps << Dependency.new(
170
+ platform: platform_name,
171
+ name: name,
172
+ requirement: version,
173
+ type: "runtime",
174
+ source: source,
175
+ integrity: pkg_hashes[name]
176
+ )
177
+ end
178
+
179
+ ParserResult.new(dependencies: deps)
180
+ end
77
181
  end
78
182
  end
79
183
  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,11 +5,16 @@ 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") => {
11
15
  kind: "manifest",
12
16
  parser: :parse_rockspec,
17
+ can_have_lockfile: false,
13
18
  },
14
19
  }
15
20
  end
@@ -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,11 +7,16 @@ 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") => {
13
17
  kind: "manifest",
14
18
  parser: :parse_manifest,
19
+ can_have_lockfile: false,
15
20
  },
16
21
  }
17
22
  end
@@ -5,12 +5,16 @@ 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") => {
11
15
  kind: 'manifest',
12
16
  parser: :parse_mlmodel,
13
- related_to: [ 'manifest' ]
17
+ can_have_lockfile: false,
14
18
  }
15
19
  }
16
20
  end
@@ -9,11 +9,16 @@ 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") => {
15
19
  kind: "manifest",
16
20
  parser: :parse_nimble,
21
+ can_have_lockfile: false,
17
22
  },
18
23
  }
19
24
  end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Bibliothecary
6
+ module Parsers
7
+ class Nix
8
+ include Bibliothecary::Analyser
9
+
10
+ def self.file_patterns
11
+ [
12
+ "flake.nix",
13
+ "flake.lock",
14
+ "nix/sources.json",
15
+ "npins/sources.json",
16
+ ]
17
+ end
18
+
19
+ def self.mapping
20
+ {
21
+ match_filename("flake.nix") => {
22
+ kind: "manifest",
23
+ parser: :parse_flake_nix,
24
+ },
25
+ match_filename("flake.lock") => {
26
+ kind: "lockfile",
27
+ parser: :parse_flake_lock,
28
+ },
29
+ match_filename("nix/sources.json") => {
30
+ kind: "lockfile",
31
+ parser: :parse_niv_sources,
32
+ },
33
+ match_filename("npins/sources.json") => {
34
+ kind: "lockfile",
35
+ parser: :parse_npins_sources,
36
+ },
37
+ }
38
+ end
39
+
40
+ # Parse flake.nix manifest file
41
+ # Extracts inputs from the Nix expression using regex
42
+ def self.parse_flake_nix(file_contents, options: {})
43
+ source = options.fetch(:filename, nil)
44
+ dependencies = []
45
+
46
+ # Pattern 1: name.url = "...";
47
+ # e.g., nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
48
+ file_contents.scan(/^\s*([\w][\w-]*)\.url\s*=\s*"([^"]+)"/) do |name, url|
49
+ requirement = parse_flake_url(url)
50
+ dependencies << Dependency.new(
51
+ name: name,
52
+ requirement: requirement,
53
+ type: "runtime",
54
+ source: source,
55
+ platform: platform_name
56
+ )
57
+ end
58
+
59
+ # Pattern 2: name = { url = "..."; ... };
60
+ # e.g., home-manager = { url = "github:nix-community/home-manager"; };
61
+ file_contents.scan(/^\s*([\w][\w-]*)\s*=\s*\{\s*\n?\s*url\s*=\s*"([^"]+)"/) do |name, url|
62
+ requirement = parse_flake_url(url)
63
+ dependencies << Dependency.new(
64
+ name: name,
65
+ requirement: requirement,
66
+ type: "runtime",
67
+ source: source,
68
+ platform: platform_name
69
+ )
70
+ end
71
+
72
+ ParserResult.new(dependencies: dependencies)
73
+ end
74
+
75
+ # Parse flake.lock lockfile
76
+ def self.parse_flake_lock(file_contents, options: {})
77
+ source = options.fetch(:filename, nil)
78
+ lock = JSON.parse(file_contents)
79
+ dependencies = []
80
+
81
+ nodes = lock.fetch("nodes", {})
82
+ root_node = nodes.fetch("root", {})
83
+ root_inputs = root_node.fetch("inputs", {})
84
+
85
+ root_inputs.each do |name, node_key|
86
+ # node_key can be a string or an array (for follows)
87
+ node_key = node_key.first if node_key.is_a?(Array)
88
+ node = nodes[node_key]
89
+ next unless node
90
+
91
+ locked = node.fetch("locked", {})
92
+ requirement = format_locked_version(locked)
93
+
94
+ dependencies << Dependency.new(
95
+ name: name,
96
+ requirement: requirement,
97
+ type: "runtime",
98
+ source: source,
99
+ platform: platform_name
100
+ )
101
+ end
102
+
103
+ ParserResult.new(dependencies: dependencies)
104
+ end
105
+
106
+ # Parse niv sources.json lockfile
107
+ def self.parse_niv_sources(file_contents, options: {})
108
+ source = options.fetch(:filename, nil)
109
+ sources = JSON.parse(file_contents)
110
+ dependencies = []
111
+
112
+ sources.each do |name, attrs|
113
+ next unless attrs.is_a?(Hash)
114
+
115
+ requirement = format_niv_version(attrs)
116
+
117
+ dependencies << Dependency.new(
118
+ name: name,
119
+ requirement: requirement,
120
+ type: "runtime",
121
+ source: source,
122
+ platform: platform_name
123
+ )
124
+ end
125
+
126
+ ParserResult.new(dependencies: dependencies)
127
+ end
128
+
129
+ # Parse npins sources.json lockfile
130
+ def self.parse_npins_sources(file_contents, options: {})
131
+ source = options.fetch(:filename, nil)
132
+ data = JSON.parse(file_contents)
133
+ dependencies = []
134
+
135
+ pins = data.fetch("pins", {})
136
+
137
+ pins.each do |name, attrs|
138
+ next unless attrs.is_a?(Hash)
139
+
140
+ requirement = format_npins_version(attrs)
141
+
142
+ dependencies << Dependency.new(
143
+ name: name,
144
+ requirement: requirement,
145
+ type: "runtime",
146
+ source: source,
147
+ platform: platform_name
148
+ )
149
+ end
150
+
151
+ ParserResult.new(dependencies: dependencies)
152
+ end
153
+
154
+ # Parse a flake URL into a version/requirement string
155
+ # Examples:
156
+ # "github:NixOS/nixpkgs/nixos-unstable" => "nixos-unstable"
157
+ # "github:NixOS/nixpkgs" => "*"
158
+ # "git+https://github.com/foo/bar?ref=v1.0" => "v1.0"
159
+ def self.parse_flake_url(url)
160
+ case url
161
+ when /^github:([^\/]+)\/([^\/\?]+)(?:\/([^\?]+))?/
162
+ $3 || "*"
163
+ when /^gitlab:([^\/]+)\/([^\/\?]+)(?:\/([^\?]+))?/
164
+ $3 || "*"
165
+ when /\?ref=([^&]+)/
166
+ $1
167
+ when /\?rev=([^&]+)/
168
+ $1
169
+ else
170
+ "*"
171
+ end
172
+ end
173
+
174
+ # Format locked version from flake.lock node
175
+ def self.format_locked_version(locked)
176
+ rev = locked["rev"]
177
+ return rev[0..6] if rev # Short commit hash
178
+
179
+ locked["version"] || "*"
180
+ end
181
+
182
+ # Format version from niv sources.json entry
183
+ def self.format_niv_version(attrs)
184
+ if attrs["rev"]
185
+ attrs["rev"][0..6]
186
+ elsif attrs["version"]
187
+ attrs["version"]
188
+ else
189
+ "*"
190
+ end
191
+ end
192
+
193
+ # Format version from npins sources.json entry
194
+ def self.format_npins_version(attrs)
195
+ if attrs["revision"]
196
+ attrs["revision"][0..6]
197
+ elsif attrs["version"]
198
+ attrs["version"]
199
+ else
200
+ "*"
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end