Skip to content

Commit f43297a

Browse files
authored
fix: handle line continuations in requirements.txt files (#195)
1 parent 5ec9266 commit f43297a

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# unescaped
2+
foo==\
3+
\
4+
\
5+
\
6+
1.2.3
7+
8+
# escaped, a literal backslash for some reason
9+
bar == 4.5\\
10+
.6
11+
12+
# comments are stripped only after line continuations are processed
13+
baz == 7.8.9 # \
14+
baz == 1.2.3
15+
16+
# continue to end
17+
qux == 10.11.12\

pkg/lockfile/parse-requirements-txt.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ func isNotRequirementLine(line string) bool {
9191
strings.HasPrefix(line, "/")
9292
}
9393

94+
func isLineContinuation(line string) bool {
95+
// checks that the line ends with an odd number of backslashes,
96+
// meaning the last one isn't escaped
97+
var re = regexp.MustCompile(`([^\\]|^)(\\{2})*\\$`)
98+
99+
return re.MatchString(line)
100+
}
101+
94102
func ParseRequirementsTxt(pathToLockfile string) ([]PackageDetails, error) {
95103
return parseRequirementsTxt(pathToLockfile, map[string]struct{}{})
96104
}
@@ -107,7 +115,17 @@ func parseRequirementsTxt(pathToLockfile string, requiredAlready map[string]stru
107115
scanner := bufio.NewScanner(file)
108116

109117
for scanner.Scan() {
110-
line := removeComments(scanner.Text())
118+
line := scanner.Text()
119+
120+
for isLineContinuation(line) {
121+
line = strings.TrimSuffix(line, "\\")
122+
123+
if scanner.Scan() {
124+
line += scanner.Text()
125+
}
126+
}
127+
128+
line = removeComments(line)
111129

112130
if ar := strings.TrimPrefix(line, "-r "); ar != line {
113131
ar = filepath.Join(filepath.Dir(pathToLockfile), ar)

pkg/lockfile/parse-requirements-txt_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,3 +525,40 @@ func TestParseRequirementsTxt_CyclicRComplex(t *testing.T) {
525525
},
526526
})
527527
}
528+
529+
func TestParseRequirementsTxt_LineContinuation(t *testing.T) {
530+
t.Parallel()
531+
532+
packages, err := lockfile.ParseRequirementsTxt("fixtures/pip/line-continuation.txt")
533+
534+
if err != nil {
535+
t.Errorf("Got unexpected error: %v", err)
536+
}
537+
538+
expectPackages(t, packages, []lockfile.PackageDetails{
539+
{
540+
Name: "foo",
541+
Version: "1.2.3",
542+
Ecosystem: lockfile.PipEcosystem,
543+
CompareAs: lockfile.PipEcosystem,
544+
},
545+
{
546+
Name: "bar",
547+
Version: "4.5\\\\",
548+
Ecosystem: lockfile.PipEcosystem,
549+
CompareAs: lockfile.PipEcosystem,
550+
},
551+
{
552+
Name: "baz",
553+
Version: "7.8.9",
554+
Ecosystem: lockfile.PipEcosystem,
555+
CompareAs: lockfile.PipEcosystem,
556+
},
557+
{
558+
Name: "qux",
559+
Version: "10.11.12",
560+
Ecosystem: lockfile.PipEcosystem,
561+
CompareAs: lockfile.PipEcosystem,
562+
},
563+
})
564+
}

0 commit comments

Comments
 (0)