-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcategory.go
More file actions
244 lines (212 loc) · 6.87 KB
/
Copy pathcategory.go
File metadata and controls
244 lines (212 loc) · 6.87 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package spdx
import (
_ "embed"
"encoding/json"
"strings"
"sync"
)
//go:embed licenses.json
var licensesJSON []byte
// Category represents a license category from scancode-licensedb.
type Category string
const (
CategoryPermissive Category = "Permissive"
CategoryCopyleft Category = "Copyleft"
CategoryCopyleftLimited Category = "Copyleft Limited"
CategoryCommercial Category = "Commercial"
CategoryProprietaryFree Category = "Proprietary Free"
CategoryPublicDomain Category = "Public Domain"
CategoryPatentLicense Category = "Patent License"
CategorySourceAvailable Category = "Source-available"
CategoryFreeRestricted Category = "Free Restricted"
CategoryCLA Category = "CLA"
CategoryUnstated Category = "Unstated License"
CategoryUnknown Category = "Unknown"
)
// licenseEntry represents a license in the scancode database.
type licenseEntry struct {
LicenseKey string `json:"license_key"`
Category string `json:"category"`
SPDXLicenseKey string `json:"spdx_license_key"`
OtherSPDXKeys []string `json:"other_spdx_license_keys"`
IsException bool `json:"is_exception"`
IsDeprecated bool `json:"is_deprecated"`
}
var (
categoryOnce sync.Once
categoryMap map[string]Category // lowercase SPDX key -> category
licenseData []licenseEntry
)
func initCategoryMap() {
categoryOnce.Do(func() {
if err := json.Unmarshal(licensesJSON, &licenseData); err != nil {
// If JSON is invalid, map will be empty
categoryMap = make(map[string]Category)
return
}
// Each entry may have a primary SPDX key, alternative keys, and a license_key.
estimatedKeys := 3
categoryMap = make(map[string]Category, len(licenseData)*estimatedKeys)
for _, entry := range licenseData {
cat := Category(entry.Category)
if cat == "" {
cat = CategoryUnknown
}
// Map primary SPDX key
if entry.SPDXLicenseKey != "" {
categoryMap[strings.ToLower(entry.SPDXLicenseKey)] = cat
}
// Map alternative SPDX keys (skip LicenseRef- ones)
for _, key := range entry.OtherSPDXKeys {
if !strings.HasPrefix(key, "LicenseRef-") {
categoryMap[strings.ToLower(key)] = cat
}
}
// Also map the license_key itself
categoryMap[strings.ToLower(entry.LicenseKey)] = cat
}
})
}
// LicenseCategory returns the category for a given license identifier.
// It accepts SPDX identifiers (like "MIT", "Apache-2.0") or scancode keys.
// Returns CategoryUnknown if the license is not found.
//
// Example:
//
// LicenseCategory("MIT") // CategoryPermissive
// LicenseCategory("GPL-3.0-only") // CategoryCopyleft
// LicenseCategory("MPL-2.0") // CategoryCopyleftLimited
func LicenseCategory(license string) Category {
initCategoryMap()
// Try exact match first
if cat, ok := categoryMap[strings.ToLower(license)]; ok {
return cat
}
// Try without -only/-or-later suffixes
license = strings.TrimSuffix(license, "-only")
license = strings.TrimSuffix(license, "-or-later")
if cat, ok := categoryMap[strings.ToLower(license)]; ok {
return cat
}
return CategoryUnknown
}
// ExpressionCategories returns all unique categories for licenses in an expression.
// It parses the expression and returns the category for each license found.
//
// Example:
//
// ExpressionCategories("MIT OR Apache-2.0")
// // []Category{CategoryPermissive} (both are Permissive)
//
// ExpressionCategories("MIT OR GPL-3.0-only")
// // []Category{CategoryPermissive, CategoryCopyleft}
func ExpressionCategories(expression string) ([]Category, error) {
licenses, err := ExtractLicenses(expression)
if err != nil {
return nil, err
}
seen := make(map[Category]bool)
var categories []Category
for _, lic := range licenses {
cat := LicenseCategory(lic)
if !seen[cat] {
seen[cat] = true
categories = append(categories, cat)
}
}
return categories, nil
}
// IsPermissive returns true if the license is in a permissive category.
// This includes Permissive, Public Domain, and similar open categories.
func IsPermissive(license string) bool {
cat := LicenseCategory(license)
return cat == CategoryPermissive || cat == CategoryPublicDomain
}
// IsCopyleft returns true if the license has copyleft requirements.
// This includes both full Copyleft and Copyleft Limited (weak copyleft).
func IsCopyleft(license string) bool {
cat := LicenseCategory(license)
return cat == CategoryCopyleft || cat == CategoryCopyleftLimited
}
// IsCommercial returns true if the license is commercial/proprietary.
func IsCommercial(license string) bool {
cat := LicenseCategory(license)
return cat == CategoryCommercial || cat == CategoryProprietaryFree
}
// HasCopyleft returns true if any license in the expression has copyleft requirements.
// This includes both full Copyleft and Copyleft Limited (weak copyleft like LGPL, MPL).
//
// Example:
//
// HasCopyleft("MIT OR Apache-2.0") // false
// HasCopyleft("MIT OR GPL-3.0-only") // true
// HasCopyleft("MIT AND LGPL-2.1-only") // true
func HasCopyleft(expression string) bool {
licenses, err := ExtractLicenses(expression)
if err != nil {
return false
}
for _, lic := range licenses {
if IsCopyleft(lic) {
return true
}
}
return false
}
// IsFullyPermissive returns true if all licenses in the expression are permissive.
// This includes Permissive and Public Domain categories.
//
// Example:
//
// IsFullyPermissive("MIT OR Apache-2.0") // true
// IsFullyPermissive("MIT AND BSD-3-Clause") // true
// IsFullyPermissive("MIT OR GPL-3.0-only") // false
func IsFullyPermissive(expression string) bool {
licenses, err := ExtractLicenses(expression)
if err != nil {
return false
}
for _, lic := range licenses {
if !IsPermissive(lic) {
return false
}
}
return len(licenses) > 0
}
// LicenseInfo contains detailed information about a license.
type LicenseInfo struct {
Key string // scancode license key
SPDXKey string // primary SPDX identifier
Category Category // license category
IsException bool // true if this is a license exception
IsDeprecated bool // true if deprecated
}
// GetLicenseInfo returns detailed information about a license.
// Returns nil if the license is not found.
func GetLicenseInfo(license string) *LicenseInfo {
initCategoryMap()
lower := strings.ToLower(license)
for _, entry := range licenseData {
// Check SPDX key
if strings.ToLower(entry.SPDXLicenseKey) == lower {
return &LicenseInfo{
Key: entry.LicenseKey,
SPDXKey: entry.SPDXLicenseKey,
Category: Category(entry.Category),
IsException: entry.IsException,
IsDeprecated: entry.IsDeprecated,
}
}
// Check license key
if strings.ToLower(entry.LicenseKey) == lower {
return &LicenseInfo{
Key: entry.LicenseKey,
SPDXKey: entry.SPDXLicenseKey,
Category: Category(entry.Category),
IsException: entry.IsException,
IsDeprecated: entry.IsDeprecated,
}
}
}
return nil
}