github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/pip/pip.go (about)

     1  package pip
     2  
     3  import (
     4  	"encoding/json"
     5  	"strings"
     6  
     7  	"github.com/apex/log"
     8  
     9  	"github.com/fossas/fossa-cli/exec"
    10  	"github.com/fossas/fossa-cli/files"
    11  )
    12  
    13  // TODO: add a Python sidecar that evaluates `setup.py`.
    14  
    15  type Pip struct {
    16  	Cmd       string
    17  	PythonCmd string
    18  }
    19  
    20  type Requirement struct {
    21  	Name     string `json:"name"`
    22  	Revision string `json:"version"`
    23  	Operator string
    24  }
    25  
    26  func (r Requirement) String() string {
    27  	return r.Name + r.Operator + r.Revision
    28  }
    29  
    30  func (p *Pip) Install(requirementsFilename string) error {
    31  	_, _, err := exec.Run(exec.Cmd{
    32  		Name: p.Cmd,
    33  		Argv: []string{"install", "-r", requirementsFilename},
    34  	})
    35  	return err
    36  }
    37  
    38  func (p *Pip) List() ([]Requirement, error) {
    39  	stdout, _, err := exec.Run(exec.Cmd{
    40  		Name: p.Cmd,
    41  		Argv: []string{"list", "--format=json"},
    42  	})
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	var reqs []Requirement
    47  	err = json.Unmarshal([]byte(stdout), &reqs)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	return reqs, nil
    52  }
    53  
    54  func FromFile(filename string) ([]Requirement, error) {
    55  	contents, err := files.Read(filename)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	var reqs []Requirement
    61  	for _, line := range strings.Split(string(contents), "\n") {
    62  		// Remove all line comments and whitespace.
    63  		commentSplit := strings.Split(line, "#")
    64  		trimmed := strings.TrimSpace(commentSplit[0])
    65  		if strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, "-") || trimmed == "" {
    66  			continue
    67  		}
    68  
    69  		log.WithField("line", line).Debug("parsing line")
    70  		// See https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format
    71  		// and https://pip.pypa.io/en/stable/reference/pip_install/#pip-install-examples
    72  		matched := false
    73  		operators := []string{"===", "<=", ">=", "==", ">", "<", "!=", "~="}
    74  		for _, op := range operators {
    75  			sections := strings.Split(trimmed, op)
    76  			if len(sections) == 2 {
    77  				reqs = append(reqs, Requirement{
    78  					Name:     checkForExtra(sections[0]),
    79  					Revision: sections[1],
    80  					Operator: op,
    81  				})
    82  				matched = true
    83  				break
    84  			}
    85  		}
    86  		if !matched {
    87  			reqs = append(reqs, Requirement{
    88  				Name: checkForExtra(trimmed),
    89  			})
    90  		}
    91  	}
    92  
    93  	return reqs, nil
    94  }
    95  
    96  // https://www.python.org/dev/peps/pep-0508/#extras
    97  func checkForExtra(name string) string {
    98  	if strings.HasSuffix(name, "]") {
    99  		i := strings.Index(name, "[")
   100  		if i > 0 {
   101  			return name[:i]
   102  		}
   103  	}
   104  	return name
   105  }