cataloger: add a requirements.txt parser

Signed-off-by: Alfredo Deza <adeza@anchore.com>
This commit is contained in:
Alfredo Deza 2020-07-21 14:25:51 -04:00
parent 8747b68b86
commit 83056a4b6f

View File

@ -0,0 +1,69 @@
package python
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/anchore/imgbom/imgbom/pkg"
)
func parseRequirementsTxt(_ string, reader io.Reader) ([]pkg.Package, error) {
packages := make([]pkg.Package, 0)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimRight(line, "\n")
switch {
case strings.HasPrefix(line, "#"):
// commented out line, skip
continue
case strings.HasPrefix(line, "-e"):
// editable packages aren't parsed (yet)
continue
case len(strings.Split(line, "==")) < 2:
// a package without a version, or a range (unpinned) which
// does not tell us exactly what will be installed
// XXX only needed if we want to log this, otherwise the next case catches it
continue
case len(strings.Split(line, "==")) == 2:
// remove comments if present
uncommented := removeTrailingComment(line)
// parse a new requirement
parts := strings.Split(uncommented, "==")
name := strings.TrimSpace(parts[0])
version := strings.TrimSpace(parts[1])
packages = append(packages, pkg.Package{
Name: name,
Version: version,
Language: pkg.Python,
Type: pkg.PythonRequirementsPkg,
})
default:
continue
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to parse python requirements file: %w", err)
}
return packages, nil
}
func removeTrailingComment(line string) string {
parts := strings.Split(line, "#")
switch len(parts) {
case 1:
// there aren't any comments
return line
default:
// any number of "#" means we only want the first part, assuming this
// isn't prefixed with "#" (up to the caller)
return parts[0]
}
}