1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
package gemspec
import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
"unicode"
"golang.org/x/xerrors"
"github.com/aquasecurity/go-dep-parser/pkg/types"
)
const specNewStr = "Gem::Specification.new"
var (
// Capture the variable name
// e.g. Gem::Specification.new do |s|
// => s
newVarRegexp = regexp.MustCompile(`\|(?P<var>.*)\|`)
// Capture the value of "name"
// e.g. s.name = "async".freeze
// => "async".freeze
nameRegexp = regexp.MustCompile(`\.name\s*=\s*(?P<name>\S+)`)
// Capture the value of "version"
// e.g. s.version = "1.2.3"
// => "1.2.3"
versionRegexp = regexp.MustCompile(`\.version\s*=\s*(?P<version>\S+)`)
// Capture the value of "license"
// e.g. s.license = "MIT"
// => "MIT"
licenseRegexp = regexp.MustCompile(`\.license\s*=\s*(?P<license>\S+)`)
// Capture the value of "licenses"
// e.g. s.license = ["MIT".freeze, "BSDL".freeze]
// => "MIT".freeze, "BSDL".freeze
licensesRegexp = regexp.MustCompile(`\.licenses\s*=\s*\[(?P<licenses>.+)\]`)
)
func Parse(r io.Reader) (types.Library, error) {
var newVar, name, version, license string
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.Contains(line, specNewStr) {
newVar = findSubString(newVarRegexp, line, "var")
}
if newVar == "" {
continue
}
// Capture name, version, license, and licenses
switch {
case strings.HasPrefix(line, fmt.Sprintf("%s.name", newVar)):
// https://guides.rubygems.org/specification-reference/#name
name = findSubString(nameRegexp, line, "name")
name = trim(name)
case strings.HasPrefix(line, fmt.Sprintf("%s.version", newVar)):
// https://guides.rubygems.org/specification-reference/#version
version = findSubString(versionRegexp, line, "version")
version = trim(version)
case strings.HasPrefix(line, fmt.Sprintf("%s.licenses", newVar)):
// https://guides.rubygems.org/specification-reference/#licenses=
license = findSubString(licensesRegexp, line, "licenses")
license = parseLicenses(license)
case strings.HasPrefix(line, fmt.Sprintf("%s.license", newVar)):
// https://guides.rubygems.org/specification-reference/#license=
license = findSubString(licenseRegexp, line, "license")
license = trim(license)
}
// No need to iterate the loop anymore
if name != "" && version != "" && license != "" {
break
}
}
if err := scanner.Err(); err != nil {
return types.Library{}, xerrors.Errorf("failed to parse gemspec: %w", err)
}
if name == "" || version == "" {
return types.Library{}, xerrors.New("failed to parse gemspec")
}
return types.Library{
Name: name,
Version: version,
License: license,
}, nil
}
func findSubString(re *regexp.Regexp, line, name string) string {
m := re.FindStringSubmatch(line)
if m == nil {
return ""
}
return m[re.SubexpIndex(name)]
}
// Trim single quotes, double quotes and ".freeze"
// e.g. "async".freeze => async
func trim(s string) string {
s = strings.TrimSuffix(s, ".freeze")
return strings.Trim(s, `'"`)
}
func parseLicenses(s string) string {
// e.g. `"Ruby".freeze, "BSDL".freeze`
// => {"\"Ruby\".freeze", "\"BSDL\".freeze"}
ss := strings.FieldsFunc(s, func(r rune) bool {
return unicode.IsSpace(r) || r == ','
})
// e.g. {"\"Ruby\".freeze", "\"BSDL\".freeze"}
// => {"Ruby", "BSDL"}
var licenses []string
for _, l := range ss {
licenses = append(licenses, trim(l))
}
return strings.Join(licenses, ", ")
}
|