github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/r/parse_description.go (about) 1 package r 2 3 import ( 4 "bufio" 5 "io" 6 "regexp" 7 "strings" 8 9 "github.com/anchore/syft/syft/artifact" 10 "github.com/anchore/syft/syft/file" 11 "github.com/anchore/syft/syft/pkg" 12 "github.com/anchore/syft/syft/pkg/cataloger/generic" 13 ) 14 15 /* some examples of license strings found in DESCRIPTION files: 16 find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'License:' | sort | uniq 17 License: GPL 18 License: GPL (>= 2) 19 License: GPL (>=2) 20 License: GPL(>=2) 21 License: GPL (>= 2) | file LICENCE 22 License: GPL-2 | GPL-3 23 License: GPL-3 24 License: LGPL (>= 2) 25 License: LGPL (>= 2.1) 26 License: MIT + file LICENSE 27 License: Part of R 4.3.0 28 License: Unlimited 29 */ 30 31 func parseDescriptionFile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 32 values := extractFieldsFromDescriptionFile(reader) 33 m := parseDataFromDescriptionMap(values) 34 p := newPackage(m, []file.Location{reader.Location}...) 35 if p.Name == "" || p.Version == "" { 36 return nil, nil, nil 37 } 38 return []pkg.Package{p}, nil, nil 39 } 40 41 type parseData struct { 42 Package string 43 Version string 44 License string 45 pkg.RDescriptionFileMetadata 46 } 47 48 func parseDataFromDescriptionMap(values map[string]string) parseData { 49 return parseData{ 50 License: values["License"], 51 Package: values["Package"], 52 Version: values["Version"], 53 RDescriptionFileMetadata: pkg.RDescriptionFileMetadata{ 54 Title: values["Title"], 55 Description: cleanMultiLineValue(values["Description"]), 56 Maintainer: values["Maintainer"], 57 URL: commaSeparatedList(values["URL"]), 58 Depends: commaSeparatedList(values["Depends"]), 59 Imports: commaSeparatedList(values["Imports"]), 60 Suggests: commaSeparatedList(values["Suggests"]), 61 NeedsCompilation: yesNoToBool(values["NeedsCompilation"]), 62 Author: values["Author"], 63 Repository: values["Repository"], 64 Built: values["Built"], 65 }, 66 } 67 } 68 69 func yesNoToBool(s string) bool { 70 /* 71 $ docker run --rm -it rocker/r-ver bash 72 $ install2.r ggplot2 dplyr mlr3 caret # just some packages for a larger sample 73 $ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'NeedsCompilation:' | sort | uniq 74 NeedsCompilation: no 75 NeedsCompilation: yes 76 $ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep 'NeedsCompilation:' | wc -l 77 105 78 */ 79 return strings.EqualFold(s, "yes") 80 } 81 82 func commaSeparatedList(s string) []string { 83 var result []string 84 split := strings.Split(s, ",") 85 for _, piece := range split { 86 value := strings.TrimSpace(piece) 87 if value == "" { 88 continue 89 } 90 result = append(result, value) 91 } 92 return result 93 } 94 95 var space = regexp.MustCompile(`\s+`) 96 97 func cleanMultiLineValue(s string) string { 98 return space.ReplaceAllString(s, " ") 99 } 100 101 func extractFieldsFromDescriptionFile(reader io.Reader) map[string]string { 102 result := make(map[string]string) 103 key := "" 104 var valueFragment strings.Builder 105 scanner := bufio.NewScanner(reader) 106 107 for scanner.Scan() { 108 line := scanner.Text() 109 // line is like Key: Value -> start capturing value; close out previous value 110 // line is like \t\t continued value -> append to existing value 111 if len(line) == 0 { 112 continue 113 } 114 if startsWithWhitespace(line) { 115 // we're continuing a value 116 if key == "" { 117 continue 118 } 119 valueFragment.WriteByte('\n') 120 valueFragment.WriteString(strings.TrimSpace(line)) 121 } else { 122 if key != "" { 123 // capture previous value 124 result[key] = valueFragment.String() 125 key = "" 126 valueFragment = strings.Builder{} 127 } 128 parts := strings.SplitN(line, ":", 2) 129 if len(parts) != 2 { 130 continue 131 } 132 key = parts[0] 133 valueFragment.WriteString(strings.TrimSpace(parts[1])) 134 } 135 } 136 if key != "" { 137 result[key] = valueFragment.String() 138 } 139 return result 140 } 141 142 func startsWithWhitespace(s string) bool { 143 if s == "" { 144 return false 145 } 146 return s[0] == ' ' || s[0] == '\t' 147 }