github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/python/parse_setup.go (about)

     1  package python
     2  
     3  import (
     4  	"bufio"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/anchore/syft/syft/artifact"
     9  	"github.com/anchore/syft/syft/file"
    10  	"github.com/anchore/syft/syft/pkg"
    11  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    12  	"github.com/lineaje-labs/syft/internal/log"
    13  )
    14  
    15  // integrity check
    16  var _ generic.Parser = parseSetup
    17  
    18  // match examples:
    19  //
    20  //	'pathlib3==2.2.0;python_version<"3.6"'  --> match(name=pathlib3 version=2.2.0)
    21  //	 "mypy==v0.770",                        --> match(name=mypy version=v0.770)
    22  //	" mypy2 == v0.770", ' mypy3== v0.770',  --> match(name=mypy2 version=v0.770), match(name=mypy3, version=v0.770)
    23  var pinnedDependency = regexp.MustCompile(`['"]\W?(\w+\W?==\W?[\w.]*)`)
    24  
    25  func parseSetup(
    26  	_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser,
    27  ) ([]pkg.Package, []artifact.Relationship, error) {
    28  	var packages []pkg.Package
    29  
    30  	scanner := bufio.NewScanner(reader)
    31  
    32  	for scanner.Scan() {
    33  		line := scanner.Text()
    34  		line = strings.TrimRight(line, "\n")
    35  
    36  		for _, match := range pinnedDependency.FindAllString(line, -1) {
    37  			parts := strings.Split(match, "==")
    38  			if len(parts) != 2 {
    39  				continue
    40  			}
    41  			name := strings.Trim(parts[0], "'\"")
    42  			name = strings.TrimSpace(name)
    43  			name = strings.Trim(name, "'\"")
    44  
    45  			version := strings.TrimSpace(parts[len(parts)-1])
    46  			version = strings.Trim(version, "'\"")
    47  
    48  			if hasTemplateDirective(name) || hasTemplateDirective(version) {
    49  				// this can happen in more dynamic setup.py where there is templating
    50  				continue
    51  			}
    52  
    53  			if name == "" || version == "" {
    54  				log.WithFields("path", reader.RealPath).Debugf("unable to parse package in setup.py line: %q", line)
    55  				continue
    56  			}
    57  
    58  			packages = append(
    59  				packages,
    60  				newPackageForIndex(
    61  					name,
    62  					version,
    63  					reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    64  				),
    65  			)
    66  		}
    67  	}
    68  
    69  	return packages, nil, nil
    70  }
    71  
    72  func hasTemplateDirective(s string) bool {
    73  	return strings.Contains(s, `%s`) || strings.Contains(s, `{`) || strings.Contains(s, `}`)
    74  }