1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
|
require 'pathname'
class Gem::RequestSet::Lockfile
##
# Raised when a lockfile cannot be parsed
class ParseError < Gem::Exception
##
# The column where the error was encountered
attr_reader :column
##
# The line where the error was encountered
attr_reader :line
##
# The location of the lock file
attr_reader :path
##
# Raises a ParseError with the given +message+ which was encountered at a
# +line+ and +column+ while parsing.
def initialize message, line, column, path
@line = line
@column = column
@path = path
super "#{message} (at #{line}:#{column})"
end
end
##
# The platforms for this Lockfile
attr_reader :platforms
##
# Creates a new Lockfile for the given +request_set+ and +gem_deps_file+
# location.
def initialize request_set, gem_deps_file
@set = request_set
@gem_deps_file = Pathname(gem_deps_file).expand_path
@gem_deps_dir = @gem_deps_file.dirname
@current_token = nil
@line = 0
@line_pos = 0
@platforms = []
@tokens = []
end
def add_DEPENDENCIES out # :nodoc:
out << "DEPENDENCIES"
@set.dependencies.sort.map do |dependency|
source = @requests.find do |req|
req.name == dependency.name and
req.spec.class == Gem::DependencyResolver::VendorSpecification
end
source_dep = '!' if source
requirement = dependency.requirement
out << " #{dependency.name}#{source_dep}#{requirement.for_lockfile}"
end
out << nil
end
def add_GEM out # :nodoc:
out << "GEM"
source_groups = @spec_groups.values.flatten.group_by do |request|
request.spec.source.uri
end
source_groups.map do |group, requests|
out << " remote: #{group}"
out << " specs:"
requests.sort_by { |request| request.name }.each do |request|
platform = "-#{request.spec.platform}" unless
Gem::Platform::RUBY == request.spec.platform
out << " #{request.name} (#{request.version}#{platform})"
request.full_spec.dependencies.sort.each do |dependency|
requirement = dependency.requirement
out << " #{dependency.name}#{requirement.for_lockfile}"
end
end
end
out << nil
end
def add_PATH out # :nodoc:
return unless path_requests =
@spec_groups.delete(Gem::DependencyResolver::VendorSpecification)
out << "PATH"
path_requests.each do |request|
directory = Pathname(request.spec.source.uri).expand_path
out << " remote: #{directory.relative_path_from @gem_deps_dir}"
out << " specs:"
out << " #{request.name} (#{request.version})"
end
out << nil
end
def add_PLATFORMS out # :nodoc:
out << "PLATFORMS"
platforms = @requests.map { |request| request.spec.platform }.uniq
platforms.delete Gem::Platform::RUBY if platforms.length > 1
platforms.each do |platform|
out << " #{platform}"
end
out << nil
end
##
# Gets the next token for a Lockfile
def get expected_type = nil, expected_value = nil # :nodoc:
@current_token = @tokens.shift
type, value, line, column = @current_token
if expected_type and expected_type != type then
unget
message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
"expected #{expected_type.inspect}"
raise ParseError.new message, line, column, "#{@gem_deps_file}.lock"
end
if expected_value and expected_value != value then
unget
message = "unexpected token [#{type.inspect}, #{value.inspect}], " +
"expected [#{expected_type.inspect}, #{expected_value.inspect}]"
raise ParseError.new message, line, column, "#{@gem_deps_file}.lock"
end
@current_token
end
def parse # :nodoc:
tokenize
until @tokens.empty? do
type, data, column, line = get
case type
when :section then
skip :newline
case data
when 'DEPENDENCIES' then
parse_DEPENDENCIES
when 'GEM' then
parse_GEM
when 'PLATFORMS' then
parse_PLATFORMS
else
type, = get until @tokens.empty? or peek.first == :section
end
else
raise "BUG: unhandled token #{type} (#{data.inspect}) at #{line}:#{column}"
end
end
end
def parse_DEPENDENCIES # :nodoc:
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
@set.gem name
skip :newline
end
end
def parse_GEM # :nodoc:
get :entry, 'remote'
_, data, = get :text
source = Gem::Source.new data
skip :newline
get :entry, 'specs'
skip :newline
set = Gem::DependencyResolver::LockSet.new source
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
case peek[0]
when :newline then # ignore
when :l_paren then
get :l_paren
_, version, = get :text
get :r_paren
set.add name, version, Gem::Platform::RUBY
else
raise "BUG: unknown token #{peek}"
end
skip :newline
end
@set.sets << set
end
def parse_PLATFORMS # :nodoc:
while not @tokens.empty? and :text == peek.first do
_, name, = get :text
@platforms << name
skip :newline
end
end
##
# Peeks at the next token for Lockfile
def peek # :nodoc:
@tokens.first
end
def skip type # :nodoc:
get while not @tokens.empty? and peek.first == type
end
def to_s
@set.resolve
out = []
@requests = @set.sorted_requests
@spec_groups = @requests.group_by do |request|
request.spec.class
end
add_PATH out
add_GEM out
add_PLATFORMS out
add_DEPENDENCIES out
out.join "\n"
end
##
# Calculates the column (by byte) and the line of the current token based on
# +byte_offset+.
def token_pos byte_offset # :nodoc:
[byte_offset - @line_pos, @line]
end
def tokenize # :nodoc:
@line = 0
@line_pos = 0
@platforms = []
@tokens = []
@current_token = nil
lock_file = "#{@gem_deps_file}.lock"
@input = File.read lock_file
s = StringScanner.new @input
until s.eos? do
pos = s.pos
# leading whitespace is for the user's convenience
next if s.scan(/ +/)
if s.scan(/[<|=>]{7}/) then
message = "your #{lock_file} contains merge conflict markers"
line, column = token_pos pos
raise ParseError.new message, line, column, lock_file
end
@tokens <<
case
when s.scan(/\r?\n/) then
token = [:newline, nil, *token_pos(pos)]
@line_pos = s.pos
@line += 1
token
when s.scan(/[A-Z]+/) then
[:section, s.matched, *token_pos(pos)]
when s.scan(/([a-z]+):\s/) then
s.pos -= 1 # rewind for possible newline
[:entry, s[1], *token_pos(pos)]
when s.scan(/\(/) then
[:l_paren, nil, *token_pos(pos)]
when s.scan(/\)/) then
[:r_paren, nil, *token_pos(pos)]
when s.scan(/[^\s)]*/) then
[:text, s.matched, *token_pos(pos)]
else
raise "BUG: can't create token for: #{s.string[s.pos..-1].inspect}"
end
end
@tokens
end
##
# Ungets the last token retrieved by #get
def unget # :nodoc:
@tokens.unshift @current_token
end
end
|