File: parse.go

package info (click to toggle)
golang-github-aquasecurity-go-dep-parser 0.0~git20220110.4a30ebc-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 19,096 kB
  • sloc: xml: 673; php: 7; makefile: 4
file content (129 lines) | stat: -rw-r--r-- 3,491 bytes parent folder | download
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, ", ")
}