-
Notifications
You must be signed in to change notification settings - Fork 525
/
Copy pathDependenciesFileParser.fs
608 lines (532 loc) · 32.5 KB
/
DependenciesFileParser.fs
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
namespace Paket
module DependenciesFileParser =
open System.Numerics
open System
open System.IO
open Requirements
open ModuleResolver
open Domain
open PackageSources
open Logging
let private operators =
VersionRange.BasicOperators
@ (VersionRange.BasicOperators |> List.map (fun o -> VersionRange.StrategyOperators |> List.map (fun s -> string s + o)) |> List.concat)
let (|NuGetStrategy|PaketStrategy|NoStrategy|) (text : string) =
match text |> Seq.tryHead with
| Some '!' -> NuGetStrategy
| Some '@' -> PaketStrategy
| _ -> NoStrategy
let parseResolverStrategy (text : string) =
match text with
| NuGetStrategy -> Some ResolverStrategy.Min
| PaketStrategy -> Some ResolverStrategy.Max
| NoStrategy -> None
let twiddle (minimum:SemVerInfo) =
let inline isNumeric (item: string) =
match BigInteger.TryParse item with
| true, number -> Some(number)
| false, _ -> None
let mutable fragments =
((minimum.AsString.Split '-').[0].Split '.')
|> Array.map isNumeric
|> Array.takeWhile (fun i -> i.IsSome)
|> Array.choose (fun i -> i)
let proIndex = Math.Max(fragments.Length - 2, 0)
fragments.[proIndex] <- fragments.[proIndex] + BigInteger.One
let promoted = fragments |> Array.take (proIndex + 1)
String.Join(".", promoted |> Array.map (fun i -> i.ToString()))
let parseVersionRequirement (text : string) : VersionRequirement =
try
let inline parsePrerelease (versions:SemVerInfo list) (texts : string list) =
let items = texts |> List.filter ((<>) "") |> List.distinct
match items with
| [] ->
versions
|> List.collect (function { PreRelease = Some x } -> [x.Name] | _ -> [])
|> List.distinct
|> function [] -> PreReleaseStatus.No | xs -> PreReleaseStatus.Concrete xs
| [x] when String.equalsIgnoreCase x "prerelease" -> PreReleaseStatus.All
| _ -> PreReleaseStatus.Concrete items
if String.IsNullOrWhiteSpace text then VersionRequirement(VersionRange.AtLeast "0",PreReleaseStatus.No) else
match text.Split([|' '|],StringSplitOptions.RemoveEmptyEntries) |> Array.toList with
| ">=" :: v1 :: "<" :: v2 :: rest ->
let v1 = SemVer.Parse v1
let v2 = SemVer.Parse v2
VersionRequirement(VersionRange.Range(VersionRangeBound.Including,v1,v2,VersionRangeBound.Excluding),parsePrerelease [v1; v2] rest)
| ">=" :: v1 :: "<=" :: v2 :: rest ->
let v1 = SemVer.Parse v1
let v2 = SemVer.Parse v2
VersionRequirement(VersionRange.Range(VersionRangeBound.Including,v1,v2,VersionRangeBound.Including),parsePrerelease [v1; v2] rest)
| "~>" :: v1 :: ">=" :: v2 :: rest ->
let v1 = SemVer.Parse(twiddle (SemVer.Parse v1))
let v2 = SemVer.Parse v2
VersionRequirement(VersionRange.Range(VersionRangeBound.Including,v2,v1,VersionRangeBound.Excluding),parsePrerelease [v1; v2] rest)
| "~>" :: v1 :: ">" :: v2 :: rest ->
let v1 = SemVer.Parse(twiddle (SemVer.Parse v1))
let v2 = SemVer.Parse v2
VersionRequirement(VersionRange.Range(VersionRangeBound.Excluding,v2,v1,VersionRangeBound.Excluding),parsePrerelease [v1; v2] rest)
| "~>" :: v1 :: "<=" :: v2 :: rest ->
let v1 = SemVer.Parse v1
let v2 = List.min [SemVer.Parse (twiddle v1); SemVer.Parse v2]
VersionRequirement(VersionRange.Range(VersionRangeBound.Including,v1,v2,VersionRangeBound.Including),parsePrerelease [v1; v2] rest)
| "~>" :: v1 :: "<" :: v2 :: rest ->
let v1 = SemVer.Parse v1
let v2 = List.min [SemVer.Parse (twiddle v1); SemVer.Parse v2]
VersionRequirement(VersionRange.Range(VersionRangeBound.Including,v1,v2,VersionRangeBound.Excluding),parsePrerelease [v1; v2] rest)
| ">" :: v1 :: "<" :: v2 :: rest ->
let v1 = SemVer.Parse v1
let v2 = SemVer.Parse v2
VersionRequirement(VersionRange.Range(VersionRangeBound.Excluding,v1,v2,VersionRangeBound.Excluding),parsePrerelease [v1; v2] rest)
| ">" :: v1 :: "<=" :: v2 :: rest ->
let v1 = SemVer.Parse v1
let v2 = SemVer.Parse v2
VersionRequirement(VersionRange.Range(VersionRangeBound.Excluding,v1,v2,VersionRangeBound.Including),parsePrerelease [v1; v2] rest)
| _ ->
let splitVersion (text:string) =
match VersionRange.BasicOperators |> List.tryFind(text.StartsWith) with
| Some token -> token, text.Replace(token + " ", "").Split ' ' |> Array.toList
| None -> "=", text.Split ' ' |> Array.toList
match splitVersion text with
| "==", version :: rest ->
let v = SemVer.Parse version
VersionRequirement(VersionRange.OverrideAll v,parsePrerelease [v] rest)
| ">=", version :: rest ->
let v = SemVer.Parse version
VersionRequirement(VersionRange.Minimum v,parsePrerelease [v] rest)
| ">", version :: rest ->
let v = SemVer.Parse version
VersionRequirement(VersionRange.GreaterThan v,parsePrerelease [v] rest)
| "<", version :: rest ->
let v = SemVer.Parse version
VersionRequirement(VersionRange.LessThan v,parsePrerelease [v] rest)
| "<=", version :: rest ->
let v = SemVer.Parse version
VersionRequirement(VersionRange.Maximum v,parsePrerelease [v] rest)
| "~>", minimum :: rest ->
let v1 = SemVer.Parse minimum
VersionRequirement(VersionRange.Between(minimum,twiddle v1),parsePrerelease [v1] rest)
| _, version :: rest ->
let v = SemVer.Parse version
VersionRequirement(VersionRange.Specific v,parsePrerelease [v] rest)
| _ -> failwithf "could not parse version range \"%s\"" text
with
| _ -> failwithf "could not parse version range \"%s\"" text
let parseDependencyLine (line:string) =
let rec parseDepLine start acc =
if start >= line.Length then acc
else
match line.[start] with
| ' ' -> parseDepLine (start+1) acc
| '"' ->
match line.IndexOf('"', start+1) with
| -1 -> failwithf "Unclosed quote in line '%s'" line
| ind -> parseDepLine (ind+1) (line.Substring(start+1, ind-start-1)::acc)
| _ ->
match line.IndexOf(' ', start+1) with
| -1 -> line.Substring(start)::acc
| ind -> parseDepLine (ind+1) (line.Substring(start, ind-start)::acc)
parseDepLine 0 []
|> List.rev
|> List.toArray
let private parseGitSource trimmed origin originTxt =
let parts = parseDependencyLine trimmed
let getParts (projectSpec : string) =
match projectSpec.Split [| ':'; '/' |] with
| [| owner; project |] -> owner, project, None
| [| owner; project; commit |] -> owner, project, Some commit
| [| owner; project; commit; commit2 |] when projectSpec.Contains (sprintf "%s/%s" commit commit2) -> owner, project, Some (sprintf "%s/%s" commit commit2)
| _ -> failwithf "invalid %s specification (getParts):%s %s (full:%s)" originTxt Environment.NewLine projectSpec trimmed
match parts with
| [| _; projectSpec; fileSpec; authKey |] -> origin, getParts projectSpec, fileSpec, (Some authKey)
| [| _; projectSpec; fileSpec |] -> origin, getParts projectSpec, fileSpec, None
| [| _; projectSpec |] -> origin, getParts projectSpec, Constants.FullProjectSourceFileName, None
| _ -> failwithf "invalid %s specification (parseGitSource):%s %s" originTxt Environment.NewLine trimmed
let private parseHttpSource trimmed =
let parts = parseDependencyLine trimmed
let getParts (projectSpec : string) fileSpec projectName authKey =
let projectSpec = projectSpec.TrimEnd '/'
let projectSpec', commit =
let start =
match projectSpec.IndexOf "://" with
| -1 -> 8 // 8 = "https://".Length
| pos -> pos + 3
match projectSpec.IndexOf('/', start) with
| -1 -> projectSpec, "/"
| pos -> projectSpec.Substring(0, pos), projectSpec.Substring pos
let splitted = projectSpec.TrimEnd('/').Split([| ':'; '/' |], StringSplitOptions.RemoveEmptyEntries)
let removeQueryString (s:string) =
match s.IndexOf '?' with
| -1 -> s
| pos -> s.Substring(0, pos)
let fileName =
if String.IsNullOrEmpty fileSpec then
let name = splitted |> Seq.last |> removeQueryString
if String.IsNullOrEmpty (Path.GetExtension name) then name + ".fs"
else name
else fileSpec
let owner =
match projectSpec'.IndexOf "://" with
| -1 -> projectSpec'
| pos -> projectSpec'.Substring(pos + 3) |> removeInvalidChars
HttpLink(projectSpec'), (owner, projectName, Some commit), fileName, authKey
match parts with
| [| _spec; url |] -> getParts url "" "" None
| [| _spec; url; fileSpec |] -> getParts url fileSpec "" None
| [| _spec; url; fileSpec; authKey |] -> getParts url fileSpec "" (Some authKey)
| _ -> failwithf "invalid http-reference specification:%s %s" Environment.NewLine trimmed
type private ParserOption =
| ReferencesMode of bool
| OmitContent of ContentCopySettings
| FrameworkRestrictions of FrameworkRestrictions
| AutodetectFrameworkRestrictions
| LicenseDownload of bool
| ImportTargets of bool
| CopyLocal of bool
| StorageConfig of PackagesFolderGroupConfig option
| SpecificVersion of bool
| CopyContentToOutputDir of CopyToOutputDirectorySettings
| GenerateLoadScripts of bool option
| ReferenceCondition of string
| Redirects of BindingRedirectsSettings option
| ResolverStrategyForTransitives of ResolverStrategy option
| ResolverStrategyForDirectDependencies of ResolverStrategy option
type RemoteParserOption =
| PackageSource of PackageSource
| Cache of Cache
let private (|Remote|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "source" _ as trimmed ->
try
let source = PackageSource.Parse trimmed
Some (Remote (RemoteParserOption.PackageSource source))
with e ->
traceWarnfn "could not parse package source %s (%s)" trimmed e.Message
reraise ()
| String.RemovePrefix "cache" _ as trimmed -> Some (Remote (RemoteParserOption.Cache (Cache.Parse trimmed)))
| _ -> None
let private (|Package|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "nuget" trimmed ->
let parts = trimmed.Trim().Replace("\"", "").Split([|' '|],StringSplitOptions.RemoveEmptyEntries) |> Seq.toList
let isVersion(text:string) =
let (result,_) = Int32.TryParse(text.[0].ToString()) in result
match parts with
| name :: operator1 :: version1 :: operator2 :: version2 :: rest
when List.exists ((=) operator1) operators && List.exists ((=) operator2) operators ->
Some (Package(name,operator1+" "+version1+" "+operator2+" "+version2, String.Join(" ",rest) |> removeComment))
| name :: operator :: version :: rest
when List.exists ((=) operator) operators ->
Some (Package(name,operator + " " + version, String.Join(" ",rest) |> removeComment))
| name :: version :: rest when isVersion version ->
Some (Package(name,version,String.Join(" ",rest) |> removeComment))
| name :: rest -> Some (Package(name,">= 0", String.Join(" ",rest) |> removeComment))
| [name] -> Some (Package(name,">= 0",""))
| _ -> failwithf "could not retrieve NuGet package from %s" trimmed
| _ -> None
let private (|CliTool|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "clitool" trimmed ->
let parts = trimmed.Trim().Replace("\"", "").Split([|' '|],StringSplitOptions.RemoveEmptyEntries) |> Seq.toList
let isVersion(text:string) =
let result,_ = Int32.TryParse(text.[0].ToString())
result
match parts with
| name :: operator1 :: version1 :: operator2 :: version2 :: rest
when List.exists ((=) operator1) operators && List.exists ((=) operator2) operators ->
Some (CliTool(name,operator1+" "+version1+" "+operator2+" "+version2, String.Join(" ",rest) |> removeComment))
| name :: operator :: version :: rest
when List.exists ((=) operator) operators ->
Some (CliTool(name,operator + " " + version, String.Join(" ",rest) |> removeComment))
| name :: version :: rest when isVersion version ->
Some (CliTool(name,version,String.Join(" ",rest) |> removeComment))
| name :: rest -> Some (CliTool(name,">= 0", String.Join(" ",rest) |> removeComment))
| [name] -> Some (CliTool(name,">= 0",""))
| _ -> failwithf "could not retrieve cli tool from %s" trimmed
| _ -> None
let private (|ExternalLock|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "external_lock" trimmed ->
let parts = trimmed.Trim().Replace("\"", "").Split([|' '|],StringSplitOptions.RemoveEmptyEntries) |> Seq.toList
match parts with
| [fileName] -> Some (ExternalLock(fileName))
| _ -> failwithf "could not retrieve external lock from %s" trimmed
| _ -> None
let private (|Empty|_|) (line:string) =
match line.Trim() with
| _ when String.IsNullOrWhiteSpace line -> Some (Empty line)
| String.RemovePrefix "version" _ as trimmed -> Some (Empty trimmed) // Parsed by the boostrapper, not paket itself
| String.RemovePrefix "//" _ -> Some (Empty line)
| String.RemovePrefix "#" _ -> Some (Empty line)
| _ -> None
let private (|ParserOptions|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "references" trimmed -> Some (ParserOptions (ParserOption.ReferencesMode (trimmed.Replace(":","").Trim() = "strict")))
| String.RemovePrefix "redirects" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "on" -> Some BindingRedirectsSettings.On
| String.EqualsIC "force" -> Some BindingRedirectsSettings.Force
| String.EqualsIC "off" -> Some BindingRedirectsSettings.Off
| _ -> None
Some (ParserOptions (ParserOption.Redirects setting))
| String.RemovePrefix "storage" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "none" -> Some PackagesFolderGroupConfig.NoPackagesFolder
| String.EqualsIC "symlink" -> Some PackagesFolderGroupConfig.SymbolicLink
| String.EqualsIC "packages" -> Some PackagesFolderGroupConfig.DefaultPackagesFolder
| _ -> None
Some (ParserOptions (ParserOption.StorageConfig setting))
| String.RemovePrefix "strategy" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "max" -> Some ResolverStrategy.Max
| String.EqualsIC "latest-patch" -> Some ResolverStrategy.LatestPatch
| String.EqualsIC "latest-minor" -> Some ResolverStrategy.LatestMinor
| String.EqualsIC "min" -> Some ResolverStrategy.Min
| _ -> None
Some (ParserOptions (ParserOption.ResolverStrategyForTransitives setting))
| String.RemovePrefix "lowest_matching" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "false" -> Some ResolverStrategy.Max
| String.EqualsIC "true" -> Some ResolverStrategy.Min
| _ -> None
Some (ParserOptions (ParserOption.ResolverStrategyForDirectDependencies setting))
| String.RemovePrefix "frameworks" trimmed
| String.RemovePrefix "framework" trimmed ->
let text = trimmed.Replace(":", "").Trim()
if text = "auto-detect" then
Some (ParserOptions (ParserOption.AutodetectFrameworkRestrictions))
else
let restrictions, _ = Requirements.parseRestrictionsLegacy true text
if String.IsNullOrWhiteSpace text |> not && restrictions = FrameworkRestriction.NoRestriction then
failwithf "Could not parse framework restriction \"%s\"" text
let options = ParserOption.FrameworkRestrictions (ExplicitRestriction restrictions)
Some (ParserOptions options)
| String.RemovePrefix "restriction" trimmed ->
let text = trimmed.Replace(":", "").Trim()
if text = "auto-detect" then
Some (ParserOptions (ParserOption.AutodetectFrameworkRestrictions))
else
let restrictions = Requirements.parseRestrictions text |> fst
if String.IsNullOrWhiteSpace text |> not && restrictions = FrameworkRestriction.NoRestriction then
failwithf "Could not parse framework restriction \"%s\"" text
let options = ParserOption.FrameworkRestrictions (ExplicitRestriction restrictions)
Some (ParserOptions options)
| String.RemovePrefix "content" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "none" -> ContentCopySettings.Omit
| String.EqualsIC "once" -> ContentCopySettings.OmitIfExisting
| _ -> ContentCopySettings.Overwrite
Some (ParserOptions (ParserOption.OmitContent setting))
| String.RemovePrefix "import_targets" trimmed -> Some (ParserOptions (ParserOption.ImportTargets(trimmed.Replace(":","").Trim() = "true")))
| String.RemovePrefix "license_download" trimmed -> Some (ParserOptions (ParserOption.LicenseDownload(trimmed.Replace(":","").Trim() = "true")))
| String.RemovePrefix "copy_local" trimmed -> Some (ParserOptions (ParserOption.CopyLocal(trimmed.Replace(":","").Trim() = "true")))
| String.RemovePrefix "specific_version" trimmed -> Some (ParserOptions (ParserOption.SpecificVersion(trimmed.Replace(":","").Trim() = "true")))
| String.RemovePrefix "copy_content_to_output_dir" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "always" -> CopyToOutputDirectorySettings.Always
| String.EqualsIC "never" -> CopyToOutputDirectorySettings.Never
| String.EqualsIC "preserve_newest" -> CopyToOutputDirectorySettings.PreserveNewest
| x -> failwithf "Unknown copy_content_to_output_dir settings: %A" x
Some (ParserOptions (ParserOption.CopyContentToOutputDir setting))
| String.RemovePrefix "condition" trimmed -> Some (ParserOptions(ParserOption.ReferenceCondition(trimmed.Replace(":","").Trim().ToUpper())))
| String.RemovePrefix "generate_load_scripts" trimmed ->
let setting =
match trimmed.Replace(":","").Trim() with
| String.EqualsIC "on" | String.EqualsIC "true" -> Some true
| String.EqualsIC "off" | String.EqualsIC "false" -> Some false
| _ -> None
Some (ParserOptions (ParserOption.GenerateLoadScripts setting))
| _ -> None
let private (|SourceFile|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "gist" _ as trimmed ->
Some (SourceFile (parseGitSource trimmed Origin.GistLink "gist"))
| String.RemovePrefix "github" _ as trimmed ->
Some (SourceFile (parseGitSource trimmed Origin.GitHubLink "github"))
| String.RemovePrefix "http" _ as trimmed ->
Some (SourceFile (parseHttpSource trimmed))
| _ -> None
let private (|Git|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "git" _ as trimmed ->
Some (Git(trimmed.Substring 4))
| String.RemovePrefix "file:" _ as trimmed ->
Some (Git trimmed)
| _ -> None
let private (|Group|_|) (line:string) =
match line.Trim() with
| String.RemovePrefix "group" _ as trimmed -> Some (Group (trimmed.Replace("group ","")))
| _ -> None
let parsePackage (sources,parent,name,version,kind,rest:string) =
let prereleases,optionsText =
if rest.Contains ":" then
// boah that's reaaaally ugly, but keeps backwards compat
let pos = rest.IndexOf ':'
let s = rest.Substring(0,pos).TrimEnd()
let pos' = s.LastIndexOf ' '
let prereleases = if pos' > 0 then s.Substring(0,pos') else ""
let s' = if prereleases <> "" then rest.Replace(prereleases,"") else rest
prereleases,s'
else
rest,""
if operators |> Seq.exists prereleases.Contains || prereleases.Contains("!") then
failwithf "Invalid prerelease version %s" prereleases
let packageName = PackageName name
let vr = (version + " " + prereleases).Trim(VersionRange.StrategyOperators |> Array.ofList)
let versionRequirement = parseVersionRequirement vr
{ Name = packageName
ResolverStrategyForTransitives =
if optionsText.Contains "strategy" then
let kvPairs = parseKeyValuePairs optionsText
match kvPairs.TryGetValue "strategy" with
| true, "max" -> Some ResolverStrategy.Max
| true, "min" -> Some ResolverStrategy.Min
| _ -> parseResolverStrategy version
else parseResolverStrategy version
ResolverStrategyForDirectDependencies =
if optionsText.Contains "lowest_matching" then
let kvPairs = parseKeyValuePairs optionsText
match kvPairs.TryGetValue "lowest_matching" with
| true, "false" -> Some ResolverStrategy.Max
| true, "true" -> Some ResolverStrategy.Min
| _ -> None
else None
Parent = parent
Graph = Set.empty
Sources = sources
Settings = InstallSettings.Parse(optionsText).AdjustWithSpecialCases packageName
TransitivePrereleases = versionRequirement.PreReleases <> PreReleaseStatus.No
VersionRequirement = versionRequirement
Kind = kind }
let parsePackageLine(sources,parent,line:string) =
match line with
| Package(name,version,rest) -> parsePackage(sources,parent,name,version,PackageRequirementKind.Package,rest)
| CliTool(name,version,rest) -> parsePackage(sources,parent,name,version,PackageRequirementKind.DotnetCliTool,rest)
| _ -> failwithf "Not a package line: %s" line
let private parseOptions (current : DependenciesGroup) options =
match options with
| ReferencesMode mode -> { current.Options with Strict = mode }
| Redirects mode -> { current.Options with Redirects = mode }
| ResolverStrategyForTransitives strategy -> { current.Options with ResolverStrategyForTransitives = strategy }
| ResolverStrategyForDirectDependencies strategy -> { current.Options with ResolverStrategyForDirectDependencies = strategy }
| StorageConfig mode -> { current.Options with Settings = { current.Options.Settings with StorageConfig = mode } }
| CopyLocal mode -> { current.Options with Settings = { current.Options.Settings with CopyLocal = Some mode } }
| SpecificVersion mode -> { current.Options with Settings = { current.Options.Settings with SpecificVersion = Some mode } }
| CopyContentToOutputDir mode -> { current.Options with Settings = { current.Options.Settings with CopyContentToOutputDirectory = Some mode } }
| LicenseDownload mode -> { current.Options with Settings = { current.Options.Settings with LicenseDownload = Some mode } }
| ImportTargets mode -> { current.Options with Settings = { current.Options.Settings with ImportTargets = Some mode } }
| FrameworkRestrictions r -> { current.Options with Settings = { current.Options.Settings with FrameworkRestrictions = r } }
| AutodetectFrameworkRestrictions -> { current.Options with Settings = { current.Options.Settings with FrameworkRestrictions = AutoDetectFramework } }
| OmitContent omit -> { current.Options with Settings = { current.Options.Settings with OmitContent = Some omit } }
| ReferenceCondition condition -> { current.Options with Settings = { current.Options.Settings with ReferenceCondition = Some condition } }
| GenerateLoadScripts mode -> { current.Options with Settings = { current.Options.Settings with GenerateLoadScripts = mode }}
let private parseLine fileName checkDuplicates (lineNo, state: DependenciesGroup list) line =
match state with
| current::other ->
let lineNo = lineNo + 1
try
match line with
| Group newGroupName ->
let newGroups =
let newGroupName = GroupName newGroupName
if current.Name = newGroupName then current::other else
match other |> List.tryFind (fun g -> g.Name = newGroupName) with
| Some g -> g::current::(other |> List.filter (fun g -> g.Name <> newGroupName))
| None -> DependenciesGroup.New(newGroupName)::current::other
lineNo,newGroups
| Empty _ -> lineNo, current::other
| Remote (RemoteParserOption.PackageSource newSource) -> lineNo, { current with Sources = current.Sources @ [newSource] |> List.distinct }::other
| Remote (RemoteParserOption.Cache newCache) ->
let newCache =
if String.IsNullOrWhiteSpace fileName then
newCache
else
let fi = FileInfo fileName
newCache.BaseOnRoot(fi.Directory.FullName)
let caches = current.Caches @ [newCache] |> List.distinct
let sources = current.Sources @ [LocalNuGet(newCache.Location,Some newCache)] |> List.distinct
lineNo, { current with Caches = caches; Sources = sources }::other
| ParserOptions options ->
lineNo,{ current with Options = parseOptions current options} ::other
| Package(name,version,rest) ->
let package = parsePackage(current.Sources,DependenciesFile(fileName,lineNo),name,version,PackageRequirementKind.Package,rest)
if checkDuplicates && current.Packages |> List.exists (fun p -> p.Name = package.Name) then
traceWarnfn "Package %O is defined more than once in group %O of %s" package.Name current.Name fileName
lineNo, { current with Packages = current.Packages @ [package] }::other
| CliTool(name,version,rest) ->
let package = parsePackage(current.Sources,DependenciesFile(fileName,lineNo),name,version,PackageRequirementKind.DotnetCliTool,rest)
if checkDuplicates && current.Packages |> List.exists (fun p -> p.Name = package.Name) then
traceWarnfn "Package %O is defined more than once in group %O of %s" package.Name current.Name fileName
lineNo, { current with Packages = current.Packages @ [package] }::other
| ExternalLock(fileName) ->
lineNo, { current with ExternalLocks = current.ExternalLocks @ [fileName] }::other
| SourceFile(origin, (owner,project, vr), path, authKey) ->
let remoteFile : UnresolvedSource = {
Owner = owner
Project = project
Version =
match vr with
| None -> VersionRestriction.NoVersionRestriction
| Some x -> VersionRestriction.Concrete x
Name = path
Origin = origin
Command = None
OperatingSystemRestriction = None
PackagePath = None
AuthKey = authKey
}
lineNo, { current with RemoteFiles = current.RemoteFiles @ [remoteFile] }::other
| Git gitConfig ->
let owner,vr,project,origin,buildCommand,operatingSystemRestriction,packagePath = Git.Handling.extractUrlParts gitConfig
let remoteFile : UnresolvedSource = {
Owner = owner
Project = project
Version =
match vr with
| None -> VersionRestriction.NoVersionRestriction
| Some x ->
try
let vr = parseVersionRequirement x
VersionRestriction.VersionRequirement vr
with
| _ -> VersionRestriction.Concrete x
Command = buildCommand
OperatingSystemRestriction = operatingSystemRestriction
PackagePath = packagePath
Name = ""
Origin = GitLink origin
AuthKey = None
}
let sources =
match packagePath with
| None -> current.Sources
| Some path ->
let root = ""
let fullPath = remoteFile.ComputeFilePath(root,current.Name,path)
let relative = (createRelativePath root fullPath).Replace("\\","/")
LocalNuGet(relative,None) :: current.Sources |> List.distinct
lineNo, { current with RemoteFiles = current.RemoteFiles @ [remoteFile]; Sources = sources }::other
| _ -> failwithf "Unrecognized token: %s" line
with
| exn -> failwithf "Error in paket.dependencies line %d%s %s" lineNo Environment.NewLine exn.Message
| [] -> failwithf "Error in paket.dependencies line %d" lineNo
let parseDependenciesFile fileName checkDuplicates lines =
let groups =
lines
|> Array.fold (parseLine fileName checkDuplicates) (0, [DependenciesGroup.New Constants.MainDependencyGroup])
|> snd
|> List.rev
|> List.fold (fun m g ->
match Map.tryFind g.Name m with
| Some group -> Map.add g.Name (g.CombineWith group) m
| None -> Map.add g.Name g m) Map.empty
fileName, groups, lines
let parseVersionString (version : string) = {
VersionRequirement = parseVersionRequirement (version.Trim(VersionRange.StrategyOperators |> Array.ofList))
ResolverStrategy = parseResolverStrategy version
}