ecosystems-bibliothecary 15.1.0 → 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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +4 -0
- data/lib/bibliothecary/parsers/cpan.rb +186 -2
- data/lib/bibliothecary/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49988f4a70c1d5dcf6b7c05bffba8d885b3e63f04545f0c524d32b46de0804f2
|
|
4
|
+
data.tar.gz: 121b4e75f934770b9967b0e3b2427ddc1dc6aaaa19f8ed0d72f1001e50d4d575
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 227adbf47ce52d6a9bb70be154f0cc204f5f327b68adeb8c91fd2e668036359eebd07d3fb940f0d4eef00128af04fb2073304147b9285589917efef1b1d5d5cf
|
|
7
|
+
data.tar.gz: ca3b36ddfa71702dae9737500dcf09ad6a9e6c4daf5758738c793614bcdcbd730a50083d081ee45a899536b0f01c1bd7d4eef90117cc3c1aeee6063a780e0b3f
|
data/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,19 @@ 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
|
+
|
|
16
29
|
## [15.1.0]
|
|
17
30
|
|
|
18
31
|
### Added
|
data/README.md
CHANGED
|
@@ -11,13 +11,29 @@ module Bibliothecary
|
|
|
11
11
|
def self.mapping
|
|
12
12
|
{
|
|
13
13
|
match_filename("META.json", case_insensitive: true) => {
|
|
14
|
-
kind: "
|
|
14
|
+
kind: "lockfile",
|
|
15
15
|
parser: :parse_json_manifest,
|
|
16
16
|
},
|
|
17
17
|
match_filename("META.yml", case_insensitive: true) => {
|
|
18
|
-
kind: "
|
|
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
|