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
@@ -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,11 +7,16 @@ 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") => {
13
17
  kind: "manifest",
14
18
  parser: :parse_manifest,
19
+ can_have_lockfile: false,
15
20
  },
16
21
  }
17
22
  end
@@ -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") => {
@@ -45,6 +49,9 @@ module Bibliothecary
45
49
  source = options.fetch(:filename, nil)
46
50
  dependencies = []
47
51
 
52
+ # Parse SPEC CHECKSUMS section to build lookup table
53
+ checksums = parse_spec_checksums(file_contents)
54
+
48
55
  # Match pod entries: " - Name (version)" or " - Name/Subspec (version)"
49
56
  # Only process lines in PODS section (before DEPENDENCIES section)
50
57
  pods_section = file_contents.split(/^DEPENDENCIES:/)[0]
@@ -56,13 +63,38 @@ module Bibliothecary
56
63
  name: base_name,
57
64
  requirement: version,
58
65
  type: "runtime",
59
- source: source
66
+ source: source,
67
+ integrity: checksums[base_name]
60
68
  )
61
69
  end
62
70
 
63
71
  ParserResult.new(dependencies: dependencies)
64
72
  end
65
73
 
74
+ def self.parse_spec_checksums(file_contents)
75
+ checksums = {}
76
+ in_checksums = false
77
+
78
+ file_contents.each_line do |line|
79
+ if line.start_with?("SPEC CHECKSUMS:")
80
+ in_checksums = true
81
+ next
82
+ end
83
+
84
+ next unless in_checksums
85
+
86
+ # End of section (blank line or new section)
87
+ break if line.strip.empty? || (line !~ /^\s/ && line.strip != "")
88
+
89
+ # Match " Name: sha1hash"
90
+ if (match = line.match(/^\s+([^:]+):\s*([a-f0-9]+)\s*$/))
91
+ checksums[match[1]] = "sha1=#{match[2]}"
92
+ end
93
+ end
94
+
95
+ checksums
96
+ end
97
+
66
98
  def self.parse_podspec(file_contents, options: {})
67
99
  source = options.fetch(:filename, nil)
68
100
  deps = []
@@ -5,12 +5,16 @@ 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") => {
11
15
  kind: 'manifest',
12
16
  parser: :parse_cog_yaml,
13
- related_to: [ 'manifest' ]
17
+ can_have_lockfile: false,
14
18
  }
15
19
  }
16
20
  end
@@ -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,15 +7,21 @@ 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") => {
13
17
  parser: :parse_conda,
14
18
  kind: "manifest",
19
+ can_have_lockfile: false,
15
20
  },
16
21
  match_filename("environment.yaml") => {
17
22
  parser: :parse_conda,
18
23
  kind: "manifest",
24
+ can_have_lockfile: false,
19
25
  },
20
26
  }
21
27
  end
@@ -8,6 +8,10 @@ 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) => {
@@ -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) => {
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bibliothecary
4
+ module Parsers
5
+ class Deb
6
+ include Bibliothecary::Analyser
7
+
8
+ def self.file_patterns
9
+ ["debian/control", "control"]
10
+ end
11
+
12
+ def self.mapping
13
+ {
14
+ match_filename("debian/control") => {
15
+ kind: "manifest",
16
+ parser: :parse_control,
17
+ can_have_lockfile: false,
18
+ },
19
+ match_filename("control") => {
20
+ kind: "manifest",
21
+ parser: :parse_control,
22
+ can_have_lockfile: false,
23
+ },
24
+ }
25
+ end
26
+
27
+ BUILD_DEP_FIELDS = %w[Build-Depends Build-Depends-Indep Build-Depends-Arch].freeze
28
+ RUNTIME_DEP_FIELDS = %w[Depends Pre-Depends Recommends Suggests].freeze
29
+
30
+ def self.parse_control(file_contents, options: {})
31
+ source = options.fetch(:filename, nil)
32
+ dependencies = []
33
+
34
+ # Parse the control file - it's in RFC 822 format with continuation lines
35
+ # Fields can span multiple lines if continuation lines start with whitespace
36
+ fields = parse_fields(file_contents)
37
+
38
+ # Build dependencies
39
+ BUILD_DEP_FIELDS.each do |field_name|
40
+ next unless fields[field_name.downcase]
41
+
42
+ parse_dependency_list(fields[field_name.downcase]).each do |dep|
43
+ dependencies << Dependency.new(
44
+ name: dep[:name],
45
+ requirement: dep[:requirement] || "*",
46
+ type: "build",
47
+ source: source,
48
+ platform: platform_name
49
+ )
50
+ end
51
+ end
52
+
53
+ # Runtime dependencies
54
+ RUNTIME_DEP_FIELDS.each do |field_name|
55
+ next unless fields[field_name.downcase]
56
+
57
+ parse_dependency_list(fields[field_name.downcase]).each do |dep|
58
+ dependencies << Dependency.new(
59
+ name: dep[:name],
60
+ requirement: dep[:requirement] || "*",
61
+ type: "runtime",
62
+ source: source,
63
+ platform: platform_name
64
+ )
65
+ end
66
+ end
67
+
68
+ ParserResult.new(dependencies: dependencies)
69
+ end
70
+
71
+ def self.parse_fields(contents)
72
+ fields = {}
73
+ current_field = nil
74
+ current_value = []
75
+
76
+ contents.each_line do |line|
77
+ if line =~ /^(\S+):\s*(.*)$/
78
+ # Save previous field
79
+ if current_field
80
+ fields[current_field] = current_value.join(" ").strip
81
+ end
82
+ current_field = $1.downcase
83
+ current_value = [$2]
84
+ elsif line =~ /^\s+(.*)$/ && current_field
85
+ # Continuation line
86
+ current_value << $1
87
+ elsif line.strip.empty?
88
+ # Empty line - save current field and reset
89
+ if current_field
90
+ fields[current_field] = current_value.join(" ").strip
91
+ end
92
+ current_field = nil
93
+ current_value = []
94
+ end
95
+ end
96
+
97
+ # Save last field
98
+ if current_field
99
+ fields[current_field] = current_value.join(" ").strip
100
+ end
101
+
102
+ fields
103
+ end
104
+
105
+ def self.parse_dependency_list(dep_string)
106
+ deps = []
107
+
108
+ # Dependencies are comma-separated
109
+ dep_string.split(/,/).each do |dep|
110
+ dep = dep.strip
111
+ next if dep.empty?
112
+ next if dep.start_with?("$") # Skip substitution variables like ${shlibs:Depends}
113
+
114
+ # Handle alternatives (pkg1 | pkg2) - just take the first one
115
+ dep = dep.split("|").first.strip
116
+
117
+ # Parse package name and optional version constraint
118
+ # Format: package (>= 1.0) or just package
119
+ if dep =~ /^(\S+)\s*\(([^)]+)\)$/
120
+ name = $1
121
+ constraint = $2.strip
122
+ deps << { name: name, requirement: constraint }
123
+ elsif dep =~ /^(\S+)$/
124
+ deps << { name: $1, requirement: nil }
125
+ end
126
+ end
127
+
128
+ deps
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,112 @@
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
+ # Build integrity lookup from jsr and npm sections
56
+ integrity_map = {}
57
+ manifest.fetch("jsr", {}).each do |key, value|
58
+ integrity_map["jsr:#{key}"] = value["integrity"] if value.is_a?(Hash) && value["integrity"]
59
+ end
60
+ manifest.fetch("npm", {}).each do |key, value|
61
+ integrity_map["npm:#{key}"] = value["integrity"] if value.is_a?(Hash) && value["integrity"]
62
+ end
63
+
64
+ dependencies = manifest.fetch("specifiers", {}).map do |specifier, resolved_version|
65
+ name, _requirement = parse_specifier(specifier)
66
+ next unless name
67
+
68
+ # Determine protocol (npm: or jsr:) and build lookup key
69
+ protocol = specifier.start_with?("jsr:") ? "jsr" : "npm"
70
+ integrity_key = "#{protocol}:#{name}@#{resolved_version}"
71
+
72
+ Dependency.new(
73
+ name: name,
74
+ requirement: resolved_version,
75
+ type: "runtime",
76
+ source: source,
77
+ platform: platform_name,
78
+ integrity: integrity_map[integrity_key]
79
+ )
80
+ end.compact
81
+
82
+ ParserResult.new(dependencies: dependencies)
83
+ end
84
+
85
+ # Parses specifiers like:
86
+ # "npm:chalk@1" => ["chalk", "1"]
87
+ # "npm:chalk" => ["chalk", "*"]
88
+ # "jsr:@std/path@^1" => ["@std/path", "^1"]
89
+ # "jsr:@std/path" => ["@std/path", "*"]
90
+ def self.parse_specifier(specifier)
91
+ return nil unless specifier.start_with?("npm:", "jsr:")
92
+
93
+ # Remove the protocol prefix
94
+ without_protocol = specifier.sub(/^(npm|jsr):/, "")
95
+
96
+ # Handle scoped packages (@scope/name@version)
97
+ if without_protocol.start_with?("@")
98
+ # Split on @ but keep the first @ for the scope
99
+ parts = without_protocol[1..].split("@", 2)
100
+ name = "@#{parts[0]}"
101
+ requirement = parts[1] || "*"
102
+ else
103
+ # Regular package (name@version)
104
+ name, requirement = without_protocol.split("@", 2)
105
+ requirement ||= "*"
106
+ end
107
+
108
+ [name, requirement]
109
+ end
110
+ end
111
+ end
112
+ 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,15 +9,21 @@ 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") => {
15
19
  kind: "manifest",
16
20
  parser: :parse_json_manifest,
21
+ can_have_lockfile: false,
17
22
  },
18
23
  match_filename("dub.sdl") => {
19
24
  kind: "manifest",
20
25
  parser: :parse_sdl_manifest,
26
+ can_have_lockfile: false,
21
27
  },
22
28
  }
23
29
  end
@@ -5,12 +5,16 @@ 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") => {
11
15
  kind: 'manifest',
12
16
  parser: :parse_dvc_yaml,
13
- related_to: [ 'manifest' ]
17
+ can_have_lockfile: false,
14
18
  }
15
19
  }
16
20
  end
@@ -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)
@@ -205,10 +209,12 @@ module Bibliothecary
205
209
  requirement: match[2].strip.split("/").first,
206
210
  type: "runtime",
207
211
  source: options.fetch(:filename, nil),
208
- platform: platform_name
212
+ platform: platform_name,
213
+ integrity: match[3].strip
209
214
  )
210
215
  end
211
- dependencies = deps.uniq
216
+ # Dedupe by name+requirement, keeping the first occurrence (h1 hash, not go.mod hash)
217
+ dependencies = deps.uniq { |d| [d.name, d.requirement] }
212
218
  ParserResult.new(dependencies: dependencies)
213
219
  end
214
220
 
@@ -12,8 +12,12 @@ 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.]+)@/
15
+ # Matches stack.yaml.lock hackage entries like: hackage: fuzzyset-0.2.4@sha256:hash,size
16
+ STACK_LOCK_REGEXP = /hackage:\s*([a-zA-Z0-9-]+)-([0-9.]+)@sha256:([a-f0-9]+)/
17
+
18
+ def self.file_patterns
19
+ ["*.cabal", "*cabal.config", "stack.yaml.lock", "cabal.project.freeze"]
20
+ end
17
21
 
18
22
  def self.mapping
19
23
  {
@@ -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
 
@@ -185,6 +193,54 @@ module Bibliothecary
185
193
  match = line.match(STACK_LOCK_REGEXP)
186
194
  next unless match
187
195
 
196
+ deps << Dependency.new(
197
+ platform: platform_name,
198
+ name: match[1],
199
+ requirement: match[2],
200
+ type: "runtime",
201
+ source: source,
202
+ integrity: "sha256=#{match[3]}"
203
+ )
204
+ end
205
+
206
+ ParserResult.new(dependencies: deps)
207
+ end
208
+
209
+ def self.parse_cabal_project_freeze(file_contents, options: {})
210
+ source = options.fetch(:filename, "cabal.project.freeze")
211
+ deps = []
212
+
213
+ # Parse constraints field which can span multiple lines
214
+ # Format: constraints: any.pkg ==version, any.pkg2 ==version2, ...
215
+ # Also handles flag constraints like: any.pkg +flag -flag2
216
+ constraints = nil
217
+ file_contents.each_line do |line|
218
+ if line =~ /^constraints:\s*(.*)/i
219
+ constraints = $1.strip
220
+ elsif line =~ /^\s+(.*)/ && constraints
221
+ constraints += " " + $1.strip
222
+ elsif line =~ /^[a-z]/i && constraints
223
+ break
224
+ end
225
+ end
226
+
227
+ return ParserResult.new(dependencies: []) unless constraints
228
+
229
+ constraints.split(",").each do |dep_str|
230
+ dep_str = dep_str.strip
231
+ next if dep_str.empty?
232
+
233
+ # Format: any.package ==version or any.package +flag -flag
234
+ # Skip flag-only entries (no version constraint)
235
+ next unless dep_str.include?("==")
236
+
237
+ # Remove "any." prefix and parse
238
+ dep_str = dep_str.sub(/^any\./, "")
239
+
240
+ # Extract name and version: "package ==version" or "package ==version +flag"
241
+ match = dep_str.match(/^([a-zA-Z][a-zA-Z0-9-]*)\s*==\s*([\d.]+)/)
242
+ next unless match
243
+
188
244
  deps << Dependency.new(
189
245
  platform: platform_name,
190
246
  name: match[1],
@@ -7,11 +7,16 @@ 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") => {
13
17
  kind: "manifest",
14
18
  parser: :parse_manifest,
19
+ can_have_lockfile: false,
15
20
  },
16
21
  }
17
22
  end