mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Expand python license scanning to cover unclaimed files (#3779)
* expand python license scanning to cover unclaimed files Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * speed up tests using the license scanner Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
da62a82413
commit
f851085668
@ -7,15 +7,18 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
"github.com/google/licensecheck"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||||
"github.com/anchore/syft/internal/cmptest"
|
"github.com/anchore/syft/internal/cmptest"
|
||||||
|
"github.com/anchore/syft/internal/licenses"
|
||||||
"github.com/anchore/syft/internal/relationship"
|
"github.com/anchore/syft/internal/relationship"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
@ -27,6 +30,11 @@ import (
|
|||||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
licenseScanner *licenses.Scanner
|
||||||
|
)
|
||||||
|
|
||||||
type CatalogTester struct {
|
type CatalogTester struct {
|
||||||
expectedPkgs []pkg.Package
|
expectedPkgs []pkg.Package
|
||||||
expectedRelationships []artifact.Relationship
|
expectedRelationships []artifact.Relationship
|
||||||
@ -44,10 +52,26 @@ type CatalogTester struct {
|
|||||||
licenseComparer cmptest.LicenseComparer
|
licenseComparer cmptest.LicenseComparer
|
||||||
packageStringer func(pkg.Package) string
|
packageStringer func(pkg.Package) string
|
||||||
customAssertions []func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship)
|
customAssertions []func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship)
|
||||||
|
context context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func Context() context.Context {
|
||||||
|
once.Do(func() {
|
||||||
|
// most of the time in testing is initializing the scanner. Let's do that just once
|
||||||
|
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
|
||||||
|
scanner, err := licenses.NewScanner(sc)
|
||||||
|
if err != nil {
|
||||||
|
panic("unable to setup licences scanner for testing")
|
||||||
|
}
|
||||||
|
licenseScanner = &scanner
|
||||||
|
})
|
||||||
|
|
||||||
|
return licenses.SetContextLicenseScanner(context.Background(), *licenseScanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCatalogTester() *CatalogTester {
|
func NewCatalogTester() *CatalogTester {
|
||||||
return &CatalogTester{
|
return &CatalogTester{
|
||||||
|
context: Context(),
|
||||||
locationComparer: cmptest.DefaultLocationComparer,
|
locationComparer: cmptest.DefaultLocationComparer,
|
||||||
licenseComparer: cmptest.DefaultLicenseComparer,
|
licenseComparer: cmptest.DefaultLicenseComparer,
|
||||||
packageStringer: stringPackage,
|
packageStringer: stringPackage,
|
||||||
@ -64,6 +88,11 @@ func NewCatalogTester() *CatalogTester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *CatalogTester) WithContext(ctx context.Context) *CatalogTester {
|
||||||
|
p.context = ctx
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@ -202,7 +231,7 @@ func (p *CatalogTester) IgnoreUnfulfilledPathResponses(paths ...string) *Catalog
|
|||||||
|
|
||||||
func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
|
func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
pkgs, relationships, err := parser(context.Background(), p.resolver, p.env, p.reader)
|
pkgs, relationships, err := parser(p.context, p.resolver, p.env, p.reader)
|
||||||
// only test for errors if explicitly requested
|
// only test for errors if explicitly requested
|
||||||
if p.wantErr != nil {
|
if p.wantErr != nil {
|
||||||
p.wantErr(t, err)
|
p.wantErr(t, err)
|
||||||
@ -215,7 +244,7 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
|||||||
|
|
||||||
resolver := NewObservingResolver(p.resolver)
|
resolver := NewObservingResolver(p.resolver)
|
||||||
|
|
||||||
pkgs, relationships, err := cataloger.Catalog(context.Background(), resolver)
|
pkgs, relationships, err := cataloger.Catalog(p.context, resolver)
|
||||||
|
|
||||||
// this is a minimum set, the resolver may return more that just this list
|
// this is a minimum set, the resolver may return more that just this list
|
||||||
for _, path := range p.expectedPathResponses {
|
for _, path := range p.expectedPathResponses {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package python
|
package python
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
@ -16,116 +15,47 @@ import (
|
|||||||
func Test_PackageCataloger(t *testing.T) {
|
func Test_PackageCataloger(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixtures []string
|
fixture string
|
||||||
expectedPackage pkg.Package
|
expectedPackages []pkg.Package
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "egg-file-no-version",
|
name: "egg-file-no-version",
|
||||||
fixtures: []string{"test-fixtures/no-version-py3.8.egg-info"},
|
fixture: "test-fixtures/site-packages/no-version",
|
||||||
expectedPackage: pkg.Package{
|
expectedPackages: []pkg.Package{
|
||||||
|
{
|
||||||
Name: "no-version",
|
Name: "no-version",
|
||||||
|
Locations: file.NewLocationSet(file.NewLocation("no-version-py3.8.egg-info")),
|
||||||
PURL: "pkg:pypi/no-version",
|
PURL: "pkg:pypi/no-version",
|
||||||
Type: pkg.PythonPkg,
|
Type: pkg.PythonPkg,
|
||||||
Language: pkg.Python,
|
Language: pkg.Python,
|
||||||
FoundBy: "python-installed-package-cataloger",
|
FoundBy: "python-installed-package-cataloger",
|
||||||
Metadata: pkg.PythonPackage{
|
Metadata: pkg.PythonPackage{
|
||||||
Name: "no-version",
|
Name: "no-version",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: ".", // requires scanning the grandparent directory to get a valid path
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "egg-info directory",
|
name: "dist-info+egg-info site-packages directory",
|
||||||
fixtures: []string{
|
fixture: "test-fixtures/site-packages/nested",
|
||||||
"test-fixtures/egg-info/PKG-INFO",
|
expectedPackages: []pkg.Package{
|
||||||
"test-fixtures/egg-info/RECORD",
|
|
||||||
"test-fixtures/egg-info/top_level.txt",
|
|
||||||
},
|
|
||||||
expectedPackage: pkg.Package{
|
|
||||||
Name: "requests",
|
|
||||||
Version: "2.22.0",
|
|
||||||
PURL: "pkg:pypi/requests@2.22.0",
|
|
||||||
Type: pkg.PythonPkg,
|
|
||||||
Language: pkg.Python,
|
|
||||||
Licenses: pkg.NewLicenseSet(
|
|
||||||
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/egg-info/PKG-INFO")),
|
|
||||||
),
|
|
||||||
FoundBy: "python-installed-package-cataloger",
|
|
||||||
Metadata: pkg.PythonPackage{
|
|
||||||
Name: "requests",
|
|
||||||
Version: "2.22.0",
|
|
||||||
Platform: "UNKNOWN",
|
|
||||||
Author: "Kenneth Reitz",
|
|
||||||
AuthorEmail: "me@kennethreitz.org",
|
|
||||||
SitePackagesRootPath: "test-fixtures",
|
|
||||||
Files: []pkg.PythonFileRecord{
|
|
||||||
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
|
||||||
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
|
||||||
{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
|
|
||||||
{Path: "requests/__pycache__/utils.cpython-38.pyc"},
|
|
||||||
{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
|
|
||||||
{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
|
|
||||||
},
|
|
||||||
TopLevelPackages: []string{"requests"},
|
|
||||||
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
|
||||||
ProvidesExtra: []string{"security", "socks"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "egg-info directory case sensitive",
|
|
||||||
fixtures: []string{
|
|
||||||
"test-fixtures/casesensitive/EGG-INFO/PKG-INFO",
|
|
||||||
"test-fixtures/casesensitive/EGG-INFO/RECORD",
|
|
||||||
"test-fixtures/casesensitive/EGG-INFO/top_level.txt",
|
|
||||||
},
|
|
||||||
expectedPackage: pkg.Package{
|
|
||||||
Name: "requests",
|
|
||||||
Version: "2.22.0",
|
|
||||||
PURL: "pkg:pypi/requests@2.22.0",
|
|
||||||
Type: pkg.PythonPkg,
|
|
||||||
Language: pkg.Python,
|
|
||||||
Licenses: pkg.NewLicenseSet(
|
|
||||||
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/casesensitive/EGG-INFO/PKG-INFO")),
|
|
||||||
),
|
|
||||||
FoundBy: "python-installed-package-cataloger",
|
|
||||||
Metadata: pkg.PythonPackage{
|
|
||||||
Name: "requests",
|
|
||||||
Version: "2.22.0",
|
|
||||||
Platform: "UNKNOWN",
|
|
||||||
Author: "Kenneth Reitz",
|
|
||||||
AuthorEmail: "me@kennethreitz.org",
|
|
||||||
SitePackagesRootPath: "test-fixtures/casesensitive",
|
|
||||||
Files: []pkg.PythonFileRecord{
|
|
||||||
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
|
||||||
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
|
||||||
{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
|
|
||||||
{Path: "requests/__pycache__/utils.cpython-38.pyc"},
|
|
||||||
{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
|
|
||||||
{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
|
|
||||||
},
|
|
||||||
TopLevelPackages: []string{"requests"},
|
|
||||||
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
|
||||||
ProvidesExtra: []string{"security", "socks"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dist-info directory",
|
|
||||||
fixtures: []string{
|
|
||||||
"test-fixtures/dist-info/METADATA",
|
|
||||||
"test-fixtures/dist-info/RECORD",
|
|
||||||
"test-fixtures/dist-info/top_level.txt",
|
|
||||||
"test-fixtures/dist-info/direct_url.json",
|
|
||||||
},
|
|
||||||
expectedPackage: pkg.Package{
|
|
||||||
Name: "pygments",
|
Name: "pygments",
|
||||||
Version: "2.6.1",
|
Version: "2.6.1",
|
||||||
PURL: "pkg:pypi/pygments@2.6.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2Fpython-test%2Ftest.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
PURL: "pkg:pypi/pygments@2.6.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2Fpython-test%2Ftest.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
Type: pkg.PythonPkg,
|
Type: pkg.PythonPkg,
|
||||||
Language: pkg.Python,
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("dist-name/dist-info/METADATA"),
|
||||||
|
file.NewLocation("dist-name/dist-info/RECORD"),
|
||||||
|
file.NewLocation("dist-name/dist-info/direct_url.json"),
|
||||||
|
file.NewLocation("dist-name/dist-info/top_level.txt"),
|
||||||
|
),
|
||||||
Licenses: pkg.NewLicenseSet(
|
Licenses: pkg.NewLicenseSet(
|
||||||
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/dist-info/METADATA")),
|
// here we only used the license that was declared in the METADATA file, we did not go searching for other licenses
|
||||||
|
// this is the better source of truth when there is no explicit LicenseFile given
|
||||||
|
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("dist-name/dist-info/METADATA")),
|
||||||
),
|
),
|
||||||
FoundBy: "python-installed-package-cataloger",
|
FoundBy: "python-installed-package-cataloger",
|
||||||
Metadata: pkg.PythonPackage{
|
Metadata: pkg.PythonPackage{
|
||||||
@ -134,10 +64,11 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
Platform: "any",
|
Platform: "any",
|
||||||
Author: "Georg Brandl",
|
Author: "Georg Brandl",
|
||||||
AuthorEmail: "georg@python.org",
|
AuthorEmail: "georg@python.org",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: "dist-name",
|
||||||
Files: []pkg.PythonFileRecord{
|
Files: []pkg.PythonFileRecord{
|
||||||
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
||||||
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
|
{Path: "Pygments-2.6.1.dist-info/LICENSE.txt", Digest: &pkg.PythonFileDigest{Algorithm: "sha256", Value: "utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
{Path: "Pygments-2.6.1.dist-info/RECORD"},
|
{Path: "Pygments-2.6.1.dist-info/RECORD"},
|
||||||
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
||||||
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
||||||
@ -151,23 +82,63 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
ProvidesExtra: []string{"html5lib", "lxml"},
|
ProvidesExtra: []string{"html5lib", "lxml"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "requests",
|
||||||
|
Version: "2.22.0",
|
||||||
|
PURL: "pkg:pypi/requests@2.22.0",
|
||||||
|
Type: pkg.PythonPkg,
|
||||||
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("egg-name/egg-info/PKG-INFO"),
|
||||||
|
file.NewLocation("egg-name/egg-info/RECORD"),
|
||||||
|
file.NewLocation("egg-name/egg-info/top_level.txt"),
|
||||||
|
),
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("egg-name/egg-info/PKG-INFO")),
|
||||||
|
),
|
||||||
|
FoundBy: "python-installed-package-cataloger",
|
||||||
|
Metadata: pkg.PythonPackage{
|
||||||
|
Name: "requests",
|
||||||
|
Version: "2.22.0",
|
||||||
|
Platform: "UNKNOWN",
|
||||||
|
Author: "Kenneth Reitz",
|
||||||
|
AuthorEmail: "me@kennethreitz.org",
|
||||||
|
SitePackagesRootPath: "egg-name",
|
||||||
|
Files: []pkg.PythonFileRecord{
|
||||||
|
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
||||||
|
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
||||||
|
{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
|
||||||
|
{Path: "requests/__pycache__/utils.cpython-38.pyc"},
|
||||||
|
{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
|
||||||
|
{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
|
||||||
|
},
|
||||||
|
TopLevelPackages: []string{"requests"},
|
||||||
|
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||||
|
ProvidesExtra: []string{"security", "socks"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dist-info directory case sensitive",
|
name: "DIST-INFO+EGG-INFO site-packages directory (case insensitive)",
|
||||||
fixtures: []string{
|
fixture: "test-fixtures/site-packages/uppercase",
|
||||||
"test-fixtures/casesensitive/DIST-INFO/METADATA",
|
expectedPackages: []pkg.Package{
|
||||||
"test-fixtures/casesensitive/DIST-INFO/RECORD",
|
{
|
||||||
"test-fixtures/casesensitive/DIST-INFO/top_level.txt",
|
|
||||||
"test-fixtures/casesensitive/DIST-INFO/direct_url.json",
|
|
||||||
},
|
|
||||||
expectedPackage: pkg.Package{
|
|
||||||
Name: "pygments",
|
Name: "pygments",
|
||||||
Version: "2.6.1",
|
Version: "2.6.1",
|
||||||
PURL: "pkg:pypi/pygments@2.6.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2Fpython-test%2Ftest.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
PURL: "pkg:pypi/pygments@2.6.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2Fpython-test%2Ftest.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
Type: pkg.PythonPkg,
|
Type: pkg.PythonPkg,
|
||||||
Language: pkg.Python,
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("dist-name/DIST-INFO/METADATA"),
|
||||||
|
file.NewLocation("dist-name/DIST-INFO/RECORD"),
|
||||||
|
file.NewLocation("dist-name/DIST-INFO/direct_url.json"),
|
||||||
|
file.NewLocation("dist-name/DIST-INFO/top_level.txt"),
|
||||||
|
),
|
||||||
Licenses: pkg.NewLicenseSet(
|
Licenses: pkg.NewLicenseSet(
|
||||||
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/casesensitive/DIST-INFO/METADATA")),
|
// here we only used the license that was declared in the METADATA file, we did not go searching for other licenses
|
||||||
|
// this is the better source of truth when there is no explicit LicenseFile given
|
||||||
|
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("dist-name/DIST-INFO/METADATA")),
|
||||||
),
|
),
|
||||||
FoundBy: "python-installed-package-cataloger",
|
FoundBy: "python-installed-package-cataloger",
|
||||||
Metadata: pkg.PythonPackage{
|
Metadata: pkg.PythonPackage{
|
||||||
@ -176,7 +147,7 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
Platform: "any",
|
Platform: "any",
|
||||||
Author: "Georg Brandl",
|
Author: "Georg Brandl",
|
||||||
AuthorEmail: "georg@python.org",
|
AuthorEmail: "georg@python.org",
|
||||||
SitePackagesRootPath: "test-fixtures/casesensitive",
|
SitePackagesRootPath: "dist-name",
|
||||||
Files: []pkg.PythonFileRecord{
|
Files: []pkg.PythonFileRecord{
|
||||||
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
||||||
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
@ -191,21 +162,67 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
RequiresPython: ">=3.5",
|
RequiresPython: ">=3.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "malformed-record",
|
Name: "requests",
|
||||||
fixtures: []string{
|
Version: "2.22.0",
|
||||||
"test-fixtures/malformed-record/dist-info/METADATA",
|
PURL: "pkg:pypi/requests@2.22.0",
|
||||||
"test-fixtures/malformed-record/dist-info/RECORD",
|
|
||||||
},
|
|
||||||
expectedPackage: pkg.Package{
|
|
||||||
Name: "pygments",
|
|
||||||
Version: "2.6.1",
|
|
||||||
PURL: "pkg:pypi/pygments@2.6.1",
|
|
||||||
Type: pkg.PythonPkg,
|
Type: pkg.PythonPkg,
|
||||||
Language: pkg.Python,
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("egg-name/EGG-INFO/PKG-INFO"),
|
||||||
|
file.NewLocation("egg-name/EGG-INFO/RECORD"),
|
||||||
|
file.NewLocation("egg-name/EGG-INFO/top_level.txt"),
|
||||||
|
),
|
||||||
Licenses: pkg.NewLicenseSet(
|
Licenses: pkg.NewLicenseSet(
|
||||||
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/malformed-record/dist-info/METADATA")),
|
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("egg-name/EGG-INFO/PKG-INFO")),
|
||||||
|
),
|
||||||
|
FoundBy: "python-installed-package-cataloger",
|
||||||
|
Metadata: pkg.PythonPackage{
|
||||||
|
Name: "requests",
|
||||||
|
Version: "2.22.0",
|
||||||
|
Platform: "UNKNOWN",
|
||||||
|
Author: "Kenneth Reitz",
|
||||||
|
AuthorEmail: "me@kennethreitz.org",
|
||||||
|
SitePackagesRootPath: "egg-name",
|
||||||
|
Files: []pkg.PythonFileRecord{
|
||||||
|
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
||||||
|
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
||||||
|
{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
|
||||||
|
{Path: "requests/__pycache__/utils.cpython-38.pyc"},
|
||||||
|
{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
|
||||||
|
{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
|
||||||
|
},
|
||||||
|
TopLevelPackages: []string{"requests"},
|
||||||
|
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||||
|
ProvidesExtra: []string{"security", "socks"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "detect licenses",
|
||||||
|
fixture: "test-fixtures/site-packages/license",
|
||||||
|
expectedPackages: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "pygments",
|
||||||
|
Version: "2.6.1",
|
||||||
|
PURL: "pkg:pypi/pygments@2.6.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2Fpython-test%2Ftest.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
Type: pkg.PythonPkg,
|
||||||
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("with-license-file-declared.dist-info/METADATA"), // the LicenseFile is declared in the METADATA file
|
||||||
|
file.NewLocation("with-license-file-declared.dist-info/RECORD"),
|
||||||
|
file.NewLocation("with-license-file-declared.dist-info/top_level.txt"),
|
||||||
|
file.NewLocation("with-license-file-declared.dist-info/direct_url.json"),
|
||||||
|
),
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.License{
|
||||||
|
Value: "BSD-3-Clause",
|
||||||
|
SPDXExpression: "BSD-3-Clause",
|
||||||
|
Type: "concluded",
|
||||||
|
// we read the path from the LicenseFile field in the METADATA file, then read the license file directly
|
||||||
|
Locations: file.NewLocationSet(file.NewLocation("with-license-file-declared.dist-info/LICENSE.txt")),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
FoundBy: "python-installed-package-cataloger",
|
FoundBy: "python-installed-package-cataloger",
|
||||||
Metadata: pkg.PythonPackage{
|
Metadata: pkg.PythonPackage{
|
||||||
@ -214,7 +231,97 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
Platform: "any",
|
Platform: "any",
|
||||||
Author: "Georg Brandl",
|
Author: "Georg Brandl",
|
||||||
AuthorEmail: "georg@python.org",
|
AuthorEmail: "georg@python.org",
|
||||||
SitePackagesRootPath: "test-fixtures/malformed-record",
|
SitePackagesRootPath: ".",
|
||||||
|
Files: []pkg.PythonFileRecord{
|
||||||
|
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
||||||
|
{Path: "with-license-file-declared.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
|
{Path: "with-license-file-declared.dist-info/LICENSE.txt", Digest: &pkg.PythonFileDigest{Algorithm: "sha256", Value: "utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
|
{Path: "with-license-file-declared.dist-info/RECORD"},
|
||||||
|
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
||||||
|
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
||||||
|
|
||||||
|
{Path: "pygments/x_util.py", Digest: &pkg.PythonFileDigest{"sha256", "qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI="}, Size: "10778"},
|
||||||
|
},
|
||||||
|
TopLevelPackages: []string{"pygments", "something_else"},
|
||||||
|
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{URL: "https://github.com/python-test/test.git", VCS: "git", CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||||
|
RequiresPython: ">=3.5",
|
||||||
|
RequiresDist: []string{"soupsieve (>1.2)", "html5lib ; extra == 'html5lib'", "lxml ; extra == 'lxml'"},
|
||||||
|
ProvidesExtra: []string{"html5lib", "lxml"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "pygments",
|
||||||
|
Version: "2.6.1",
|
||||||
|
PURL: "pkg:pypi/pygments@2.6.1?vcs_url=git%2Bhttps%3A%2F%2Fgithub.com%2Fpython-test%2Ftest.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
Type: pkg.PythonPkg,
|
||||||
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("without-license-file-declared.dist-info/METADATA"), // the LicenseFile is declared in the METADATA file
|
||||||
|
file.NewLocation("without-license-file-declared.dist-info/RECORD"),
|
||||||
|
file.NewLocation("without-license-file-declared.dist-info/top_level.txt"),
|
||||||
|
file.NewLocation("without-license-file-declared.dist-info/direct_url.json"),
|
||||||
|
),
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.License{
|
||||||
|
Value: "BSD-3-Clause",
|
||||||
|
SPDXExpression: "BSD-3-Clause",
|
||||||
|
Type: "concluded",
|
||||||
|
// we discover license files automatically
|
||||||
|
Locations: file.NewLocationSet(file.NewLocation("without-license-file-declared.dist-info/LICENSE.txt")),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FoundBy: "python-installed-package-cataloger",
|
||||||
|
Metadata: pkg.PythonPackage{
|
||||||
|
Name: "Pygments",
|
||||||
|
Version: "2.6.1",
|
||||||
|
Platform: "any",
|
||||||
|
Author: "Georg Brandl",
|
||||||
|
AuthorEmail: "georg@python.org",
|
||||||
|
SitePackagesRootPath: ".",
|
||||||
|
Files: []pkg.PythonFileRecord{
|
||||||
|
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
||||||
|
{Path: "without-license-file-declared.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
|
{Path: "without-license-file-declared.dist-info/LICENSE.txt", Digest: &pkg.PythonFileDigest{Algorithm: "sha256", Value: "utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
|
{Path: "without-license-file-declared.dist-info/RECORD"},
|
||||||
|
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
||||||
|
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
||||||
|
|
||||||
|
{Path: "pygments/x_util.py", Digest: &pkg.PythonFileDigest{"sha256", "qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI="}, Size: "10778"},
|
||||||
|
},
|
||||||
|
TopLevelPackages: []string{"pygments", "something_else"},
|
||||||
|
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{URL: "https://github.com/python-test/test.git", VCS: "git", CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||||
|
RequiresPython: ">=3.5",
|
||||||
|
RequiresDist: []string{"soupsieve (>1.2)", "html5lib ; extra == 'html5lib'", "lxml ; extra == 'lxml'"},
|
||||||
|
ProvidesExtra: []string{"html5lib", "lxml"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed-record",
|
||||||
|
fixture: "test-fixtures/site-packages/malformed-record",
|
||||||
|
expectedPackages: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "pygments",
|
||||||
|
Version: "2.6.1",
|
||||||
|
PURL: "pkg:pypi/pygments@2.6.1",
|
||||||
|
Type: pkg.PythonPkg,
|
||||||
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("dist-info/METADATA"),
|
||||||
|
file.NewLocation("dist-info/RECORD"),
|
||||||
|
),
|
||||||
|
Licenses: pkg.NewLicenseSet(
|
||||||
|
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("dist-info/METADATA")),
|
||||||
|
),
|
||||||
|
FoundBy: "python-installed-package-cataloger",
|
||||||
|
Metadata: pkg.PythonPackage{
|
||||||
|
Name: "Pygments",
|
||||||
|
Version: "2.6.1",
|
||||||
|
Platform: "any",
|
||||||
|
Author: "Georg Brandl",
|
||||||
|
AuthorEmail: "georg@python.org",
|
||||||
|
SitePackagesRootPath: ".",
|
||||||
Files: []pkg.PythonFileRecord{
|
Files: []pkg.PythonFileRecord{
|
||||||
{Path: "flask/json/tag.py", Digest: &pkg.PythonFileDigest{"sha256", "9ehzrmt5k7hxf7ZEK0NOs3swvQyU9fWNe-pnYe69N60"}, Size: "8223"},
|
{Path: "flask/json/tag.py", Digest: &pkg.PythonFileDigest{"sha256", "9ehzrmt5k7hxf7ZEK0NOs3swvQyU9fWNe-pnYe69N60"}, Size: "8223"},
|
||||||
{Path: "../../Scripts/flask.exe", Digest: &pkg.PythonFileDigest{"sha256", "mPrbVeZCDX20himZ_bRai1nCs_tgr7jHIOGZlcgn-T4"}, Size: "93063"},
|
{Path: "../../Scripts/flask.exe", Digest: &pkg.PythonFileDigest{"sha256", "mPrbVeZCDX20himZ_bRai1nCs_tgr7jHIOGZlcgn-T4"}, Size: "93063"},
|
||||||
@ -225,19 +332,24 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// in cases where the metadata file is available and the record is not we should still record there is a package
|
// in cases where the metadata file is available and the record is not we should still record there is a package
|
||||||
// additionally empty top_level.txt files should not result in an error
|
// additionally empty top_level.txt files should not result in an error
|
||||||
name: "partial dist-info directory",
|
name: "partial dist-info directory",
|
||||||
fixtures: []string{"test-fixtures/partial.dist-info/METADATA"},
|
fixture: "test-fixtures/site-packages/partial.dist-info",
|
||||||
expectedPackage: pkg.Package{
|
expectedPackages: []pkg.Package{
|
||||||
|
{
|
||||||
Name: "pygments",
|
Name: "pygments",
|
||||||
Version: "2.6.1",
|
Version: "2.6.1",
|
||||||
PURL: "pkg:pypi/pygments@2.6.1",
|
PURL: "pkg:pypi/pygments@2.6.1",
|
||||||
Type: pkg.PythonPkg,
|
Type: pkg.PythonPkg,
|
||||||
Language: pkg.Python,
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("METADATA"),
|
||||||
|
),
|
||||||
Licenses: pkg.NewLicenseSet(
|
Licenses: pkg.NewLicenseSet(
|
||||||
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/partial.dist-info/METADATA")),
|
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("METADATA")),
|
||||||
),
|
),
|
||||||
FoundBy: "python-installed-package-cataloger",
|
FoundBy: "python-installed-package-cataloger",
|
||||||
Metadata: pkg.PythonPackage{
|
Metadata: pkg.PythonPackage{
|
||||||
@ -246,22 +358,27 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
Platform: "any",
|
Platform: "any",
|
||||||
Author: "Georg Brandl",
|
Author: "Georg Brandl",
|
||||||
AuthorEmail: "georg@python.org",
|
AuthorEmail: "georg@python.org",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: ".",
|
||||||
RequiresPython: ">=3.5",
|
RequiresPython: ">=3.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "egg-info regular file",
|
name: "egg-info regular file",
|
||||||
fixtures: []string{"test-fixtures/test.egg-info"},
|
fixture: "test-fixtures/site-packages/test",
|
||||||
expectedPackage: pkg.Package{
|
expectedPackages: []pkg.Package{
|
||||||
|
{
|
||||||
Name: "requests",
|
Name: "requests",
|
||||||
Version: "2.22.0",
|
Version: "2.22.0",
|
||||||
PURL: "pkg:pypi/requests@2.22.0",
|
PURL: "pkg:pypi/requests@2.22.0",
|
||||||
Type: pkg.PythonPkg,
|
Type: pkg.PythonPkg,
|
||||||
Language: pkg.Python,
|
Language: pkg.Python,
|
||||||
|
Locations: file.NewLocationSet(
|
||||||
|
file.NewLocation("test.egg-info"),
|
||||||
|
),
|
||||||
Licenses: pkg.NewLicenseSet(
|
Licenses: pkg.NewLicenseSet(
|
||||||
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/test.egg-info")),
|
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test.egg-info")),
|
||||||
),
|
),
|
||||||
FoundBy: "python-installed-package-cataloger",
|
FoundBy: "python-installed-package-cataloger",
|
||||||
Metadata: pkg.PythonPackage{
|
Metadata: pkg.PythonPackage{
|
||||||
@ -270,26 +387,20 @@ func Test_PackageCataloger(t *testing.T) {
|
|||||||
Platform: "UNKNOWN",
|
Platform: "UNKNOWN",
|
||||||
Author: "Kenneth Reitz",
|
Author: "Kenneth Reitz",
|
||||||
AuthorEmail: "me@kennethreitz.org",
|
AuthorEmail: "me@kennethreitz.org",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: ".",
|
||||||
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||||
ProvidesExtra: []string{"security", "socks"},
|
ProvidesExtra: []string{"security", "socks"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
resolver := file.NewMockResolverForPaths(test.fixtures...)
|
|
||||||
|
|
||||||
locations, err := resolver.FilesByPath(test.fixtures...)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
test.expectedPackage.Locations = file.NewLocationSet(locations...)
|
|
||||||
|
|
||||||
pkgtest.NewCatalogTester().
|
pkgtest.NewCatalogTester().
|
||||||
WithResolver(resolver).
|
FromDirectory(t, test.fixture).
|
||||||
Expects([]pkg.Package{test.expectedPackage}, nil).
|
Expects(test.expectedPackages, nil).
|
||||||
TestCataloger(t, NewInstalledPackageCataloger())
|
TestCataloger(t, NewInstalledPackageCataloger())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -311,7 +422,7 @@ func Test_PackageCataloger_IgnorePackage(t *testing.T) {
|
|||||||
t.Run(test.MetadataFixture, func(t *testing.T) {
|
t.Run(test.MetadataFixture, func(t *testing.T) {
|
||||||
resolver := file.NewMockResolverForPaths(test.MetadataFixture)
|
resolver := file.NewMockResolverForPaths(test.MetadataFixture)
|
||||||
|
|
||||||
actual, _, err := NewInstalledPackageCataloger().Catalog(context.Background(), resolver)
|
actual, _, err := NewInstalledPackageCataloger().Catalog(pkgtest.Context(), resolver)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if len(actual) != 0 {
|
if len(actual) != 0 {
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
package python
|
package python
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/internal/licenses"
|
|
||||||
"github.com/anchore/syft/internal/log"
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
@ -92,38 +89,6 @@ func newPackageForPackage(m parsedData, licenses pkg.LicenseSet, sources ...file
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func findLicenses(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, m parsedData) pkg.LicenseSet {
|
|
||||||
var licenseSet pkg.LicenseSet
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case m.LicenseExpression != "":
|
|
||||||
licenseSet = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(m.LicenseLocation, m.LicenseExpression)...)
|
|
||||||
case m.Licenses != "":
|
|
||||||
licenseSet = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(m.LicenseLocation, m.Licenses)...)
|
|
||||||
case m.LicenseLocation.Path() != "":
|
|
||||||
// If we have a license file then resolve and parse it
|
|
||||||
found, err := resolver.FilesByPath(m.LicenseLocation.Path())
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields("error", err, "path", m.LicenseLocation.Path()).Trace("unable to resolve python license")
|
|
||||||
}
|
|
||||||
if len(found) > 0 {
|
|
||||||
metadataContents, err := resolver.FileContentsByLocation(found[0])
|
|
||||||
if err == nil {
|
|
||||||
parsed, err := scanner.PkgSearch(ctx, file.NewLocationReadCloser(m.LicenseLocation, metadataContents))
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields("error", err, "path", m.LicenseLocation.Path()).Trace("unable to parse a license from the file")
|
|
||||||
}
|
|
||||||
if len(parsed) > 0 {
|
|
||||||
licenseSet = pkg.NewLicenseSet(parsed...)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.WithFields("error", err, "path", m.LicenseLocation.Path()).Trace("unable to read file contents")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return licenseSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func packageURL(name, version string, m *pkg.PythonPackage) string {
|
func packageURL(name, version string, m *pkg.PythonPackage) string {
|
||||||
// generate a purl from the package data
|
// generate a purl from the package data
|
||||||
pURL := packageurl.NewPackageURL(
|
pURL := packageurl.NewPackageURL(
|
||||||
|
|||||||
@ -6,7 +6,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/scylladb/go-set/strset"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/licenses"
|
"github.com/anchore/syft/internal/licenses"
|
||||||
@ -247,3 +252,97 @@ func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Lo
|
|||||||
pd.DirectURLOrigin = d
|
pd.DirectURLOrigin = d
|
||||||
return &pd, sources, nil
|
return &pd, sources, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findLicenses(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, m parsedData) pkg.LicenseSet {
|
||||||
|
var licenseSet pkg.LicenseSet
|
||||||
|
|
||||||
|
licenseLocations := file.NewLocationSet()
|
||||||
|
if m.LicenseFilePath != "" {
|
||||||
|
locs, err := resolver.FilesByPath(m.LicenseFilePath)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", m.LicenseFilePath).Trace("unable to resolve python license file")
|
||||||
|
} else {
|
||||||
|
licenseLocations.Add(locs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case m.LicenseExpression != "" || m.Licenses != "":
|
||||||
|
licenseSet = getLicenseSetFromValues(licenseLocations.ToSlice(), m.LicenseExpression, m.Licenses)
|
||||||
|
case !licenseLocations.Empty():
|
||||||
|
licenseSet = getLicenseSetFromFiles(ctx, scanner, resolver, licenseLocations.ToSlice()...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// search for known license paths from RECORDS file
|
||||||
|
licenseNames := strset.New()
|
||||||
|
for _, n := range licenses.FileNames() {
|
||||||
|
licenseNames.Add(strings.ToLower(n))
|
||||||
|
}
|
||||||
|
parent := path.Base(path.Dir(m.DistInfoLocation.Path()))
|
||||||
|
candidatePaths := strset.New()
|
||||||
|
for _, f := range m.Files {
|
||||||
|
if !strings.HasPrefix(f.Path, parent) || strings.Count(f.Path, "/") > 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if licenseNames.Has(strings.ToLower(filepath.Base(f.Path))) {
|
||||||
|
candidatePaths.Add(path.Join(m.SitePackagesRootPath, f.Path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := candidatePaths.List()
|
||||||
|
sort.Strings(paths)
|
||||||
|
locationSet := file.NewLocationSet()
|
||||||
|
for _, p := range paths {
|
||||||
|
locs, err := resolver.FilesByPath(p)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", p).Trace("unable to resolve python license in dist-info")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
locationSet.Add(locs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
licenseSet = getLicenseSetFromFiles(ctx, scanner, resolver, locationSet.ToSlice()...)
|
||||||
|
}
|
||||||
|
return licenseSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLicenseSetFromValues(locations []file.Location, licenseValues ...string) pkg.LicenseSet {
|
||||||
|
if len(locations) == 0 {
|
||||||
|
return pkg.NewLicenseSet(pkg.NewLicensesFromValues(licenseValues...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
licenseSet := pkg.NewLicenseSet()
|
||||||
|
for _, value := range licenseValues {
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
licenseSet.Add(pkg.NewLicenseFromLocations(value, locations...))
|
||||||
|
}
|
||||||
|
return licenseSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLicenseSetFromFiles(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, locations ...file.Location) pkg.LicenseSet {
|
||||||
|
licenseSet := pkg.NewLicenseSet()
|
||||||
|
for _, loc := range locations {
|
||||||
|
licenseSet.Add(getLicenseSetFromFile(ctx, scanner, resolver, loc)...)
|
||||||
|
}
|
||||||
|
return licenseSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLicenseSetFromFile(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, location file.Location) []pkg.License {
|
||||||
|
metadataContents, err := resolver.FileContentsByLocation(location)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", location.Path()).Trace("unable to read file contents")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer internal.CloseAndLogError(metadataContents, location.Path())
|
||||||
|
parsed, err := scanner.PkgSearch(ctx, file.NewLocationReadCloser(location, metadataContents))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err, "path", location.Path()).Trace("unable to parse a license from the file")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|||||||
@ -15,11 +15,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type parsedData struct {
|
type parsedData struct {
|
||||||
|
// core info
|
||||||
|
|
||||||
|
// DistInfoLocation is the location of the METADATA file within the .dist-info directory where we obtained the python package information
|
||||||
|
DistInfoLocation file.Location
|
||||||
|
pkg.PythonPackage `mapstructure:",squash"`
|
||||||
|
|
||||||
|
// license info
|
||||||
|
|
||||||
Licenses string `mapstructure:"License"`
|
Licenses string `mapstructure:"License"`
|
||||||
LicenseFile string `mapstructure:"LicenseFile"`
|
LicenseFile string `mapstructure:"LicenseFile"`
|
||||||
LicenseExpression string `mapstructure:"LicenseExpression"`
|
LicenseExpression string `mapstructure:"LicenseExpression"`
|
||||||
LicenseLocation file.Location
|
LicenseFilePath string
|
||||||
pkg.PythonPackage `mapstructure:",squash"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluralFields = map[string]bool{
|
var pluralFields = map[string]bool{
|
||||||
@ -45,11 +52,13 @@ func parseWheelOrEggMetadata(locationReader file.LocationReadCloser) (parsedData
|
|||||||
|
|
||||||
pd.SitePackagesRootPath = determineSitePackagesRootPath(path)
|
pd.SitePackagesRootPath = determineSitePackagesRootPath(path)
|
||||||
if pd.Licenses != "" || pd.LicenseExpression != "" {
|
if pd.Licenses != "" || pd.LicenseExpression != "" {
|
||||||
pd.LicenseLocation = file.NewLocation(path)
|
pd.LicenseFilePath = path
|
||||||
} else if pd.LicenseFile != "" {
|
} else if pd.LicenseFile != "" {
|
||||||
pd.LicenseLocation = file.NewLocation(filepath.Join(filepath.Dir(path), pd.LicenseFile))
|
pd.LicenseFilePath = filepath.Join(filepath.Dir(path), pd.LicenseFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pd.DistInfoLocation = locationReader.Location
|
||||||
|
|
||||||
return pd, nil
|
return pd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,38 +22,40 @@ func TestParseWheelEggMetadata(t *testing.T) {
|
|||||||
ExpectedMetadata parsedData
|
ExpectedMetadata parsedData
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Fixture: "test-fixtures/egg-info/PKG-INFO",
|
Fixture: "test-fixtures/site-packages/nested/egg-name/egg-info/PKG-INFO",
|
||||||
ExpectedMetadata: parsedData{
|
ExpectedMetadata: parsedData{
|
||||||
"Apache 2.0",
|
DistInfoLocation: file.NewLocation("test-fixtures/site-packages/nested/egg-name/egg-info/PKG-INFO"),
|
||||||
"",
|
Licenses: "Apache 2.0",
|
||||||
"",
|
LicenseFile: "",
|
||||||
file.NewLocation("test-fixtures/egg-info/PKG-INFO"),
|
LicenseExpression: "",
|
||||||
pkg.PythonPackage{
|
LicenseFilePath: "test-fixtures/site-packages/nested/egg-name/egg-info/PKG-INFO",
|
||||||
|
PythonPackage: pkg.PythonPackage{
|
||||||
Name: "requests",
|
Name: "requests",
|
||||||
Version: "2.22.0",
|
Version: "2.22.0",
|
||||||
Platform: "UNKNOWN",
|
Platform: "UNKNOWN",
|
||||||
Author: "Kenneth Reitz",
|
Author: "Kenneth Reitz",
|
||||||
AuthorEmail: "me@kennethreitz.org",
|
AuthorEmail: "me@kennethreitz.org",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: "test-fixtures/site-packages/nested/egg-name",
|
||||||
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
RequiresPython: ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||||
ProvidesExtra: []string{"security", "socks"},
|
ProvidesExtra: []string{"security", "socks"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Fixture: "test-fixtures/dist-info/METADATA",
|
Fixture: "test-fixtures/site-packages/nested/dist-name/dist-info/METADATA",
|
||||||
ExpectedMetadata: parsedData{
|
ExpectedMetadata: parsedData{
|
||||||
"BSD License",
|
DistInfoLocation: file.NewLocation("test-fixtures/site-packages/nested/dist-name/dist-info/METADATA"),
|
||||||
"",
|
Licenses: "BSD License",
|
||||||
"",
|
LicenseFile: "",
|
||||||
file.NewLocation("test-fixtures/dist-info/METADATA"),
|
LicenseExpression: "",
|
||||||
pkg.PythonPackage{
|
LicenseFilePath: "test-fixtures/site-packages/nested/dist-name/dist-info/METADATA",
|
||||||
|
PythonPackage: pkg.PythonPackage{
|
||||||
Name: "Pygments",
|
Name: "Pygments",
|
||||||
Version: "2.6.1",
|
Version: "2.6.1",
|
||||||
Platform: "any",
|
Platform: "any",
|
||||||
Author: "Georg Brandl",
|
Author: "Georg Brandl",
|
||||||
AuthorEmail: "georg@python.org",
|
AuthorEmail: "georg@python.org",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: "test-fixtures/site-packages/nested/dist-name",
|
||||||
RequiresPython: ">=3.5",
|
RequiresPython: ">=3.5",
|
||||||
RequiresDist: []string{"soupsieve (>1.2)", "html5lib ; extra == 'html5lib'", "lxml ; extra == 'lxml'"},
|
RequiresDist: []string{"soupsieve (>1.2)", "html5lib ; extra == 'html5lib'", "lxml ; extra == 'lxml'"},
|
||||||
ProvidesExtra: []string{"html5lib", "lxml"},
|
ProvidesExtra: []string{"html5lib", "lxml"},
|
||||||
@ -149,16 +151,17 @@ func TestParseWheelEggMetadataInvalid(t *testing.T) {
|
|||||||
ExpectedMetadata parsedData
|
ExpectedMetadata parsedData
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Fixture: "test-fixtures/egg-info/PKG-INFO-INVALID",
|
Fixture: "test-fixtures/site-packages/nested/egg-name/egg-info/PKG-INFO-INVALID",
|
||||||
ExpectedMetadata: parsedData{
|
ExpectedMetadata: parsedData{
|
||||||
"",
|
DistInfoLocation: file.NewLocation("test-fixtures/site-packages/nested/egg-name/egg-info/PKG-INFO-INVALID"),
|
||||||
"",
|
Licenses: "",
|
||||||
"",
|
LicenseExpression: "",
|
||||||
file.Location{},
|
LicenseFile: "",
|
||||||
pkg.PythonPackage{
|
LicenseFilePath: "",
|
||||||
|
PythonPackage: pkg.PythonPackage{
|
||||||
Name: "mxnet",
|
Name: "mxnet",
|
||||||
Version: "1.8.0",
|
Version: "1.8.0",
|
||||||
SitePackagesRootPath: "test-fixtures",
|
SitePackagesRootPath: "test-fixtures/site-packages/nested/egg-name",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,7 +15,7 @@ func TestParseWheelEggRecord(t *testing.T) {
|
|||||||
ExpectedMetadata []pkg.PythonFileRecord
|
ExpectedMetadata []pkg.PythonFileRecord
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Fixture: "test-fixtures/egg-info/RECORD",
|
Fixture: "test-fixtures/site-packages/nested/egg-name/egg-info/RECORD",
|
||||||
ExpectedMetadata: []pkg.PythonFileRecord{
|
ExpectedMetadata: []pkg.PythonFileRecord{
|
||||||
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
||||||
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
||||||
@ -26,10 +26,11 @@ func TestParseWheelEggRecord(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Fixture: "test-fixtures/dist-info/RECORD",
|
Fixture: "test-fixtures/site-packages/nested/dist-name/dist-info/RECORD",
|
||||||
ExpectedMetadata: []pkg.PythonFileRecord{
|
ExpectedMetadata: []pkg.PythonFileRecord{
|
||||||
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
||||||
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
|
{Path: "Pygments-2.6.1.dist-info/LICENSE.txt", Digest: &pkg.PythonFileDigest{Algorithm: "sha256", Value: "utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY"}, Size: "8449"},
|
||||||
{Path: "Pygments-2.6.1.dist-info/RECORD"},
|
{Path: "Pygments-2.6.1.dist-info/RECORD"},
|
||||||
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
||||||
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Pygments
|
||||||
|
Version: 2.6.1
|
||||||
|
Summary: Pygments is a syntax highlighting package written in Python.
|
||||||
|
Home-page: https://pygments.org/
|
||||||
|
Author: Georg Brandl
|
||||||
|
Author-email: georg@python.org
|
||||||
|
LicenseFile: LICENSE.txt
|
||||||
|
Keywords: syntax highlighting
|
||||||
|
Platform: any
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: End Users/Desktop
|
||||||
|
Classifier: Intended Audience :: System Administrators
|
||||||
|
Classifier: Development Status :: 6 - Mature
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Topic :: Text Processing :: Filters
|
||||||
|
Classifier: Topic :: Utilities
|
||||||
|
Requires-Python: >=3.5
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: soupsieve (>1.2)
|
||||||
|
Provides-Extra: html5lib
|
||||||
|
Requires-Dist: html5lib ; extra == 'html5lib'
|
||||||
|
Provides-Extra: lxml
|
||||||
|
Requires-Dist: lxml ; extra == 'lxml'
|
||||||
|
|
||||||
|
|
||||||
|
Pygments
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Pygments is a syntax highlighting package written in Python.
|
||||||
|
|
||||||
|
It is a generic syntax highlighter suitable for use in code hosting, forums,
|
||||||
|
wikis or other applications that need to prettify source code. Highlights
|
||||||
|
are:
|
||||||
|
|
||||||
|
* a wide range of over 500 languages and other text formats is supported
|
||||||
|
* special attention is paid to details, increasing quality by a fair amount
|
||||||
|
* support for new languages and formats are added easily
|
||||||
|
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences
|
||||||
|
* it is usable as a command-line tool and as a library
|
||||||
|
|
||||||
|
:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
|
||||||
|
with-license-file-declared.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
|
||||||
|
with-license-file-declared.dist-info/LICENSE.txt,sha256=utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY,8449
|
||||||
|
with-license-file-declared.dist-info/RECORD,,
|
||||||
|
pygments/__pycache__/__init__.cpython-38.pyc,,
|
||||||
|
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
|
||||||
|
pygments/x_util.py,sha256=qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI=,10778
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Pygments
|
||||||
|
Version: 2.6.1
|
||||||
|
Summary: Pygments is a syntax highlighting package written in Python.
|
||||||
|
Home-page: https://pygments.org/
|
||||||
|
Author: Georg Brandl
|
||||||
|
Author-email: georg@python.org
|
||||||
|
Keywords: syntax highlighting
|
||||||
|
Platform: any
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: End Users/Desktop
|
||||||
|
Classifier: Intended Audience :: System Administrators
|
||||||
|
Classifier: Development Status :: 6 - Mature
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Topic :: Text Processing :: Filters
|
||||||
|
Classifier: Topic :: Utilities
|
||||||
|
Requires-Python: >=3.5
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: soupsieve (>1.2)
|
||||||
|
Provides-Extra: html5lib
|
||||||
|
Requires-Dist: html5lib ; extra == 'html5lib'
|
||||||
|
Provides-Extra: lxml
|
||||||
|
Requires-Dist: lxml ; extra == 'lxml'
|
||||||
|
|
||||||
|
|
||||||
|
Pygments
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Pygments is a syntax highlighting package written in Python.
|
||||||
|
|
||||||
|
It is a generic syntax highlighter suitable for use in code hosting, forums,
|
||||||
|
wikis or other applications that need to prettify source code. Highlights
|
||||||
|
are:
|
||||||
|
|
||||||
|
* a wide range of over 500 languages and other text formats is supported
|
||||||
|
* special attention is paid to details, increasing quality by a fair amount
|
||||||
|
* support for new languages and formats are added easily
|
||||||
|
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences
|
||||||
|
* it is usable as a command-line tool and as a library
|
||||||
|
|
||||||
|
:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
|
||||||
|
without-license-file-declared.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
|
||||||
|
without-license-file-declared.dist-info/LICENSE.txt,sha256=utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY,8449
|
||||||
|
without-license-file-declared.dist-info/RECORD,,
|
||||||
|
pygments/__pycache__/__init__.cpython-38.pyc,,
|
||||||
|
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
|
||||||
|
pygments/x_util.py,sha256=qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI=,10778
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
Copyright 2010 Pallets
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@ -1,5 +1,6 @@
|
|||||||
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
|
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
|
||||||
Pygments-2.6.1.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
|
Pygments-2.6.1.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
|
||||||
|
Pygments-2.6.1.dist-info/LICENSE.txt,sha256=utiUvpzxqFPVpvuPnWG2_Oku6BGuay2I8-NIrqCvqUY,8449
|
||||||
Pygments-2.6.1.dist-info/RECORD,,
|
Pygments-2.6.1.dist-info/RECORD,,
|
||||||
pygments/__pycache__/__init__.cpython-38.pyc,,
|
pygments/__pycache__/__init__.cpython-38.pyc,,
|
||||||
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
|
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"url": "https://github.com/python-test/test.git", "vcs_info": {"commit_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "vcs": "git"}}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
pygments
|
||||||
|
something_else
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"url": "https://github.com/python-test/test.git", "vcs_info": {"commit_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "vcs": "git"}}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
pygments
|
||||||
|
something_else
|
||||||
Loading…
x
Reference in New Issue
Block a user