Skip to content
This repository has been archived by the owner on Dec 5, 2024. It is now read-only.

Commit

Permalink
Fix paket.lock file parser (#38)
Browse files Browse the repository at this point in the history
* Fix paket.lock file parser

Description
===========

The `paket.lock` file parser has issues with counting leading
whitespaces. The value from the old logic `def newLeadingWhitespaces =
(line =~ /\s/).size()` returns the number of all white space characters
in the current line.

The second part:

```groovy
if (newLeadingWhitespaces > currentLeadingWhitespaces) {
  currentIndent++
} else if (newLeadingWhitespaces < currentLeadingWhitespaces) {
  currentIndent--
}
```

didn't take into account when the indention level suddenly jumped from 3
to 1. I also made the method `List<String>
getAllDependencies(List<String> references)` recursive.
I added a new test covering the failing behavior

Changes
=======

![FIX] `paket.lock` file parsing
![IMPROVE] `getAllDependencies` method by making it recursive
![ADD] new test example

* Refactor the lock parser some more and add more test cases

I switched from `if` statements to `switch/case` because of esthetical
reasons. I added some more guards around the switch arms to limit
crashes. We might need to add a proper parser with error results if and
when we want to support multi type lock files.
  • Loading branch information
Larusso authored Apr 18, 2018
1 parent 5635348 commit ba02eb8
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,47 +40,49 @@ class PaketLock {
PaketLock(String lockContent) {
content = [:]

def currentSourceType
def currentPackageName
def currentIndent = 0
def currentLeadingWhitespaces = 0
def currentLineData
String currentSourceType
String currentPackageName
int currentIndent = 0
String currentLineData

lockContent.eachLine { line ->

if (!line.trim()) {
return
}

def newLeadingWhitespaces = (line =~ /\s/).size()
if (newLeadingWhitespaces > currentLeadingWhitespaces) {
currentIndent++
} else if (newLeadingWhitespaces < currentLeadingWhitespaces) {
currentIndent--
}
currentLeadingWhitespaces = newLeadingWhitespaces
currentLineData = line.trim()

if (currentIndent == LineType.TYPE.value && isValidSourceType(currentLineData)) {
currentSourceType = currentLineData
content[currentSourceType] = [:]
return
}
if (currentIndent == LineType.REMOTE.value) {
return
}
if (currentIndent == LineType.NAME.value) {
currentPackageName = currentLineData.split(" ")[0]
content["${currentSourceType}"][currentPackageName] = []
if (currentLineData.trim().empty) {
return
}
if (currentIndent == LineType.DEPENDENCY.value) {
(content["${currentSourceType}"]["${currentPackageName}"] as List<String>) << currentLineData.split(" ")[0]

def match = (line =~ /^[\s]+/)
currentIndent = !match ? 0 : match[0].size() / 2

switch (currentIndent) {
case LineType.TYPE.value:
if(isValidSourceType(currentLineData)) {
currentSourceType = currentLineData
content[currentSourceType] = [:]
}
break
case LineType.NAME.value:
currentPackageName = currentLineData.split(/\s/)[0]
if(currentSourceType && currentPackageName) {
content[currentSourceType][currentPackageName] = []
}
break

case LineType.DEPENDENCY.value:
if(currentSourceType && currentPackageName) {
(content[currentSourceType][currentPackageName] as List<String>) << currentLineData.split(/\s/)[0]
}
break

case LineType.REMOTE.value:
default:
break
}
}
}

boolean isValidSourceType(String value) {
static boolean isValidSourceType(String value) {

for (SourceType type in SourceType.values()) {
if (type.value == value) return true
Expand All @@ -89,22 +91,14 @@ class PaketLock {
}

List<String> getDependencies(SourceType source, String id) {
content[source.getValue()] && content[source.getValue()][id] ? content[source.getValue()][id] as List<String> : null
content[source.getValue()] && content[source.getValue()][id] ? content[source.getValue()][id] as List<String> : []
}

List<String> getAllDependencies(List<String> references) {
def result = []
for (def referenceDependency in references) {
result.add(referenceDependency)
def referenceDependencies = getDependencies(SourceType.NUGET, referenceDependency)
if (referenceDependencies) {
result.addAll(referenceDependencies)
def dependencies = getAllDependencies(referenceDependencies)
if (dependencies) {
result.addAll(dependencies)
}
}
def ref = references.collect { reference ->
[reference, getAllDependencies(getDependencies(SourceType.NUGET, reference))]
}
result.unique()

ref.flatten().unique()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,70 @@ class PaketLockSpec extends Specification {

static String LOCK_CONTENT = """
NUGET
remote: https://wooga.artifactoryonline.com/wooga/api/nuget/atlas-nuget
NSubstitute (1.9.1.0)
Substance (0.0.10-beta)
Substance.Collections (0.0.10-beta)
Substance (>= 0.0.10-beta)
Substance.Collections.Generic (0.0.10-beta)
Substance (>= 0.0.10-beta)
Substance.Collections (>= 0.0.10-beta)
Substance.Collections.Immutable (0.0.10-beta)
Substance (>= 0.0.10-beta)
Substance.Collections (>= 0.0.10-beta)
Substance.Collections.Generic (>= 0.0.10-beta)
Wooga.AtlasBuildTools (1.0.0)
Wooga.Lambda (0.7.0)
Substance.Collections.Immutable (>= 0.0.0-beta)
Wooga.XCodeEditor (3.1.3)
Wooga.JsonDotNetNode (>= 0.1.0-prerelease < 0.2.0-prerelease)
remote: https://wooga.artifactoryonline.com/wooga/api/nuget/atlas-nuget-snapshot
Wooga.JsonDotNetNode (0.1.1-master00001)
remote: https://a.repo.com
A (0.0.10-beta)
W.A (3.0.0-rc00001)
W.S (>= 3.0.0-rc)
W.E (1.0.0)
W.N (0.1.0)
W.S (3.0.1)
W.J (>= 0.1.0-prerelease < 0.2.0-prerelease)
W.L (>= 0.7 < 1.0)
remote: https://www.nuget.org/api/v2
A.C (0.0.10-beta)
A (>= 0.0.10-beta)
A.C.G (0.0.10-beta)
A (>= 0.0.10-beta)
A.C (>= 0.0.10-beta)
A.C.I (0.0.10-beta)
A (>= 0.0.10-beta)
A.C (>= 0.0.10-beta)
A.C.G (>= 0.0.10-beta)
W.L (0.7)
A.C.I (>= 0.0.0-beta)
remote: https://a.repo.com/snapshot
W.J (0.1.1-master00001)
""".stripIndent()

static String MULTI_TYPE_LOCK_CONTENT = """
${LOCK_CONTENT}
GITHUB
remote: fsharp/FAKE
modules/Octokit/Octokit.fsx (a25c2f256a99242c1106b5a3478aae6bb68c7a93)
Octokit (>= 0)
GIT
remote: https://github.com/forki/nupkgtest.git
(05366e390e7552a569f3f328a0f3094249f3b93b)
HTTP
remote: http://www.fssnip.net/raw/1M/test1.fs
test1.fs
""".stripIndent()

static String LOCK_CONTENT_BROKEN_TYPE = """
NUGIT
remote: https://a.repo.com
A (0.0.10-beta)
W.A (3.0.0-rc00001)
W.S (>= 3.0.0-rc)
W.E (1.0.0)
W.N (0.1.0)
W.S (3.0.1)
W.J (>= 0.1.0-prerelease < 0.2.0-prerelease)
W.L (>= 0.7 < 1.0)
""".stripIndent()

static String LOCK_CONTENT_BROKEN_INDENTION = """
NUGET
remote: https://a.repo.com
A (0.0.10-beta)
W.A (3.0.0-rc00001)
W.S (>= 3.0.0-rc)
W.E (1.0.0)
W.N (0.1.0)
W.S (3.0.1)
W.J (>= 0.1.0-prerelease < 0.2.0-prerelease)
W.L (>= 0.7 < 1.0)
""".stripIndent()

@Shared
Expand All @@ -49,14 +94,63 @@ class PaketLockSpec extends Specification {
def lock = new PaketLock(content)

then:
def nugets = lock.getDependencies(PaketLock.SourceType.NUGET, "Wooga.XCodeEditor")
def nugets = lock.getDependencies(PaketLock.SourceType.NUGET, "W.A")
nugets.size() == 1
nugets.contains("Wooga.JsonDotNetNode")
nugets.contains("W.S")

where:
objectType | content
"String" | LOCK_CONTENT
"File" | lockFile << LOCK_CONTENT
"String" | LOCK_CONTENT
"File" | lockFile << LOCK_CONTENT
"multi type String" | MULTI_TYPE_LOCK_CONTENT
"multi type File" | lockFile << MULTI_TYPE_LOCK_CONTENT
}

@Unroll
def "parses all nuget dependencies from paket.lock with #objectType"() {
when:
def lock = new PaketLock(content)

then:
def nugets = lock.getAllDependencies(references)

expectedDependencies.every {
nugets.contains(it)
}

nugets.size() == expectedDependencies.size()

where:
objectType | content
"String" | LOCK_CONTENT
"File" | lockFile << LOCK_CONTENT
"multi type String" | MULTI_TYPE_LOCK_CONTENT
"multi type File" | lockFile << MULTI_TYPE_LOCK_CONTENT

references = ["W.S", "W.A", "W.N", "W.E"]
expectedDependencies = references + ["A", "A.C", "A.C.I", "A.C.G", "W.L", "W.J"]
}

@Unroll
def "fails to expand when dependency file has #failure"() {
when:
def lock = new PaketLock(content)

then:
def nugets = lock.getAllDependencies(references)

expectedDependencies.every {
nugets.contains(it)
}

nugets.size() == expectedDependencies.size()

where:
failure | content
"broken type" | LOCK_CONTENT_BROKEN_TYPE
"wrong indention" | LOCK_CONTENT_BROKEN_INDENTION
references = ["A"]
expectedDependencies = references
}

}

0 comments on commit ba02eb8

Please sign in to comment.