Select and pull a devfile using Alizer (#5464)

* Add alizer library and test functionality

<!--
Thank you for opening a PR! Here are some things you need to know before submitting:

1. Please read our developer guideline: https://github.com/redhat-developer/odo/wiki/Developer-Guidelines
2. Label this PR accordingly with the '/kind' line
3. Ensure you have written and ran the appropriate tests: https://github.com/redhat-developer/odo/wiki/Writing-and-running-tests
4. Read how we approve and LGTM each PR: https://github.com/redhat-developer/odo/wiki/PR-Review

Documentation:

If you are pushing a change to documentation, please read: https://github.com/redhat-developer/odo/wiki/Contributing-to-Docs
-->

**What type of PR is this:**

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind tests
/kind documentation

Feel free to use other [labels](https://github.com/redhat-developer/odo/labels) as needed. However one of the above labels must be present or the PR will not be reviewed. This instruction is for reviewers as well.
-->

/kind feature

**What does this PR do / why we need it:**

Adds the alizer library from
https://github.com/redhat-developer/alizer/tree/main/go as part of our
implementaion of `odo dev` and `odo init`.

This builds upon @feloy 's PR located here: https://github.com/redhat-developer/odo/pull/5434

**Which issue(s) this PR fixes:**
<!--
Specifying the issue will automatically close it when this PR is merged
-->

Fixes #

**PR acceptance criteria:**

- [X] Unit test

- [X] Integration test

- [X] Documentation

**How to test changes / Special notes to the reviewer:**

N/A. Only function implementation

* New alizer version

* Use alizer for odo init

* Add integration tests

* Add Alizer to odo deploy

* review

* Ask component name for odo deploy

* Fix unit test

Co-authored-by: Charlie Drage <charlie@charliedrage.com>
This commit is contained in:
Philippe Martin
2022-02-23 07:52:51 +01:00
committed by GitHub
parent 2bd4f073e7
commit ff03b8e49a
74 changed files with 13226 additions and 120 deletions

1
go.mod
View File

@@ -35,6 +35,7 @@ require (
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.1.1
github.com/redhat-developer/alizer/go v0.0.0-20220215154256-33df7feef4ae
github.com/redhat-developer/service-binding-operator v0.9.0
github.com/securego/gosec/v2 v2.8.0
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect

6
go.sum
View File

@@ -1007,6 +1007,10 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA=
github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redhat-developer/alizer/go v0.0.0-20220204121940-dec463ed3af3 h1:PFDkd6xGpa1SwWMj4bKrHc2xPAwA0x2PKZOlo1LfWYQ=
github.com/redhat-developer/alizer/go v0.0.0-20220204121940-dec463ed3af3/go.mod h1:18H8Trq+vkpxqek82e7SimdNTdFfOGGjN7gAUGDO3jU=
github.com/redhat-developer/alizer/go v0.0.0-20220215154256-33df7feef4ae h1:N2wsIYtziHQ51GNcJY5YcB0YldpR5BwPoTvby+l0vy8=
github.com/redhat-developer/alizer/go v0.0.0-20220215154256-33df7feef4ae/go.mod h1:EKkrP0Am7Xt/yg3dF8uH1SSoOcaZmBom8Iy6CJPPDok=
github.com/redhat-developer/service-binding-operator v0.9.0 h1:CS+eEtzu/PtWuyvYQFQpZXd6ukSuFtN+U0EKKtTsvlA=
github.com/redhat-developer/service-binding-operator v0.9.0/go.mod h1:D415gZQiz5Q8zyRbmrNrlieb6Xp73oFtCb+nCuTL6GA=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
@@ -1299,6 +1303,8 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@@ -242,6 +242,7 @@ func createRegistryDevfiles(registry Registry, devfileIndex []indexSchema.Schema
Registry: registry,
Language: devfileIndexEntry.Language,
Tags: devfileIndexEntry.Tags,
ProjectType: devfileIndexEntry.ProjectType,
}
registryDevfiles = append(registryDevfiles, stackDevfile)
}

View File

@@ -16,6 +16,7 @@ type DevfileComponentType struct {
Registry Registry
Language string
Tags []string
ProjectType string
}
// DevfileComponentTypeList lists all the DevfileComponentType's

View File

@@ -1,8 +1,10 @@
package location
import (
"os"
"path/filepath"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/redhat-developer/odo/pkg/util"
)
@@ -30,3 +32,47 @@ func DevfileLocation(contexDir string) string {
devFile := DevfileFilenamesProvider(contexDir)
return filepath.Join(contexDir, devFile)
}
// IsDevfileName returns true if name is a supported name for a devfile
func IsDevfileName(name string) bool {
for _, devFile := range possibleDevfileNames {
if devFile == name {
return true
}
}
return false
}
// DirectoryContainsDevfile returns true if the given directory contains a devfile with a supported name
func DirectoryContainsDevfile(fsys filesystem.Filesystem, dir string) (bool, error) {
for _, devFile := range possibleDevfileNames {
_, err := fsys.Stat(filepath.Join(dir, devFile))
if os.IsNotExist(err) {
continue
} else if err != nil {
return false, err
}
// path to file does exist
return true, nil
}
return false, nil
}
// DirIsEmpty returns true if the given directory contains no file
func DirIsEmpty(fsys filesystem.Filesystem, path string) (bool, error) {
files, err := fsys.ReadDir(path)
if err != nil {
return false, err
}
return len(files) == 0, nil
}
// DirContainsOnlyDevfile returns true if the directory contains only one file which is a devfile
// with a supported name
func DirContainsOnlyDevfile(fsys filesystem.Filesystem, path string) (bool, error) {
files, err := fsys.ReadDir(path)
if err != nil {
return false, err
}
return len(files) == 1 && IsDevfileName(files[0].Name()), nil
}

View File

@@ -77,3 +77,16 @@ func (o *Survey) AskName(defaultName string) (string, error) {
}
return answer, nil
}
func (o *Survey) AskCorrect() (bool, error) {
question := &survey.Confirm{
Message: "Is this correct?",
Default: true,
}
var answer bool
err := survey.AskOne(question, &answer)
if err != nil {
return false, err
}
return answer, nil
}

View File

@@ -19,4 +19,7 @@ type Asker interface {
// AskName asks for a devfile component name
AskName(defaultName string) (string, error)
// AskCorrect asks for confirmation
AskCorrect() (bool, error)
}

View File

@@ -34,6 +34,21 @@ func (m *MockAsker) EXPECT() *MockAskerMockRecorder {
return m.recorder
}
// AskCorrect mocks base method.
func (m *MockAsker) AskCorrect() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AskCorrect")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AskCorrect indicates an expected call of AskCorrect.
func (mr *MockAskerMockRecorder) AskCorrect() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AskCorrect", reflect.TypeOf((*MockAsker)(nil).AskCorrect))
}
// AskLanguage mocks base method.
func (m *MockAsker) AskLanguage(langs []string) (string, error) {
m.ctrl.T.Helper()

View File

@@ -0,0 +1,92 @@
package backend
import (
"fmt"
"reflect"
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/parser"
"github.com/redhat-developer/alizer/go/pkg/apis/recognizer"
"github.com/redhat-developer/odo/pkg/catalog"
"github.com/redhat-developer/odo/pkg/init/asker"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
type AlizerBackend struct {
askerClient asker.Asker
catalogClient catalog.Client
}
func NewAlizerBackend(askerClient asker.Asker, catalogClient catalog.Client) *AlizerBackend {
return &AlizerBackend{
askerClient: askerClient,
catalogClient: catalogClient,
}
}
func (o *AlizerBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
return nil
}
// detectFramework uses the alizer library in order to detect the devfile
// to use depending on the files in the path
func (o *AlizerBackend) detectFramework(path string) (recognizer.DevFileType, catalog.Registry, error) {
types := []recognizer.DevFileType{}
components, err := o.catalogClient.ListDevfileComponents("")
if err != nil {
return recognizer.DevFileType{}, catalog.Registry{}, err
}
for _, component := range components.Items {
types = append(types, recognizer.DevFileType{
Name: component.Name,
Language: component.Language,
ProjectType: component.ProjectType,
Tags: component.Tags,
})
}
typ, err := recognizer.SelectDevFileFromTypes(path, types)
if err != nil {
return recognizer.DevFileType{}, catalog.Registry{}, err
}
// TODO(feloy): This part won't be necessary when SelectDevFileFromTypes returns the index
var indexOfDetected int
for i, typeFromList := range types {
if reflect.DeepEqual(typeFromList, typ) {
indexOfDetected = i
break
}
}
registry := components.Items[indexOfDetected].Registry
return typ, registry, nil
}
// SelectDevfile calls thz Alizer to detect the devfile and asks for confirmation to the user
func (o *AlizerBackend) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (location *DevfileLocation, err error) {
selected, registry, err := o.detectFramework(dir)
if err != nil {
return nil, err
}
fmt.Printf("Based on the files in the current directory odo detected\nLanguage: %s\nProject type: %s\n", selected.Language, selected.ProjectType)
fmt.Printf("The devfile %q from the registry %q will be downloaded.\n", selected.Name, registry.Name)
confirm, err := o.askerClient.AskCorrect()
if err != nil {
return nil, err
}
if !confirm {
return nil, nil
}
return &DevfileLocation{
Devfile: selected.Name,
DevfileRegistry: registry.Name,
}, nil
}
func (o *AlizerBackend) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (starter *v1alpha2.StarterProject, err error) {
return nil, nil
}
func (o *AlizerBackend) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error {
return nil
}

View File

@@ -0,0 +1,218 @@
package backend
import (
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/golang/mock/gomock"
"github.com/redhat-developer/odo/pkg/catalog"
"github.com/redhat-developer/odo/pkg/init/asker"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
// Below functions are from:
// https://github.com/redhat-developer/alizer/blob/main/go/test/apis/language_recognizer_test.go
func GetTestProjectPath(folder string) string {
_, b, _, _ := runtime.Caller(0)
basepath := filepath.Dir(b)
return filepath.Join(basepath, "..", "..", "..", "tests/examples/source/", folder)
}
var types = []catalog.DevfileComponentType{
{
Name: "java-maven",
Language: "java",
ProjectType: "maven",
Tags: []string{"Java", "Maven"},
Registry: catalog.Registry{
Name: "registry1",
},
},
{
Name: "java-quarkus",
Language: "java",
ProjectType: "quarkus",
Tags: []string{"Java", "Quarkus"},
Registry: catalog.Registry{
Name: "registry1",
},
},
{
Name: "java-wildfly",
Language: "java",
ProjectType: "wildfly",
Tags: []string{"Java", "WildFly"},
Registry: catalog.Registry{
Name: "registry2",
},
},
{
Name: "nodejs",
Language: "javascript",
ProjectType: "nodejs",
Tags: []string{"NodeJS", "Express", "ubi8"},
Registry: catalog.Registry{
Name: "registry2",
},
},
{
Name: "python",
Language: "python",
ProjectType: "python",
Tags: []string{"Python", "pip"},
Registry: catalog.Registry{
Name: "registry3",
},
},
}
var list = catalog.DevfileComponentTypeList{
Items: types,
}
func TestDetectFramework(t *testing.T) {
type args struct {
path string
}
tests := []struct {
name string
args args
wantedDevfile string
wantedRegistry string
wantErr bool
}{
{
name: "Detect Node.JS example",
args: args{
path: GetTestProjectPath("nodejs"),
},
wantedDevfile: "nodejs",
wantedRegistry: "registry2",
wantErr: false,
},
{
name: "Detect java openjdk example",
args: args{
path: GetTestProjectPath("openjdk"),
},
wantedDevfile: "java-maven",
wantedRegistry: "registry1",
wantErr: false,
},
{
name: "Detect python example",
args: args{
path: GetTestProjectPath("python"),
},
wantedDevfile: "python",
wantedRegistry: "registry3",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
askerClient := asker.NewMockAsker(ctrl)
catalogClient := catalog.NewMockClient(ctrl)
catalogClient.EXPECT().ListDevfileComponents("").Return(list, nil)
alizerClient := NewAlizerBackend(askerClient, catalogClient)
// Run function DetectFramework
detected, registry, err := alizerClient.detectFramework(tt.args.path)
if !tt.wantErr == (err != nil) {
t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
return
}
if detected.Name != tt.wantedDevfile {
t.Errorf("unexpected devfile %v, wantedDevfile %v", detected, tt.wantedDevfile)
}
if registry.Name != tt.wantedRegistry {
t.Errorf("unexpected registry %v, wantedRegistry %v", registry, tt.wantedRegistry)
}
})
}
}
func TestAlizerBackend_SelectDevfile(t *testing.T) {
type fields struct {
askerClient func(ctrl *gomock.Controller) asker.Asker
catalogClient func(ctrl *gomock.Controller) catalog.Client
}
type args struct {
flags map[string]string
fs filesystem.Filesystem
dir string
}
tests := []struct {
name string
fields fields
args args
wantLocation *DevfileLocation
wantErr bool
}{
{
name: "devfile found and accepted",
fields: fields{
askerClient: func(ctrl *gomock.Controller) asker.Asker {
askerClient := asker.NewMockAsker(ctrl)
askerClient.EXPECT().AskCorrect().Return(true, nil)
return askerClient
},
catalogClient: func(ctrl *gomock.Controller) catalog.Client {
catalogClient := catalog.NewMockClient(ctrl)
catalogClient.EXPECT().ListDevfileComponents("").Return(list, nil)
return catalogClient
},
},
args: args{
fs: filesystem.DefaultFs{},
dir: GetTestProjectPath("nodejs"),
},
wantLocation: &DevfileLocation{
Devfile: "nodejs",
DevfileRegistry: "registry2",
},
},
{
name: "devfile found but not accepted",
fields: fields{
askerClient: func(ctrl *gomock.Controller) asker.Asker {
askerClient := asker.NewMockAsker(ctrl)
askerClient.EXPECT().AskCorrect().Return(false, nil)
return askerClient
},
catalogClient: func(ctrl *gomock.Controller) catalog.Client {
catalogClient := catalog.NewMockClient(ctrl)
catalogClient.EXPECT().ListDevfileComponents("").Return(list, nil)
return catalogClient
},
},
args: args{
fs: filesystem.DefaultFs{},
dir: GetTestProjectPath("nodejs"),
},
wantLocation: nil,
},
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
o := &AlizerBackend{
askerClient: tt.fields.askerClient(ctrl),
catalogClient: tt.fields.catalogClient(ctrl),
}
gotLocation, err := o.SelectDevfile(tt.args.flags, tt.args.fs, tt.args.dir)
if (err != nil) != tt.wantErr {
t.Errorf("AlizerBackend.SelectDevfile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotLocation, tt.wantLocation) {
t.Errorf("AlizerBackend.SelectDevfile() = %v, want %v", gotLocation, tt.wantLocation)
}
})
}
}

View File

@@ -9,7 +9,9 @@ import (
"github.com/devfile/library/pkg/devfile/parser/data/v2/common"
dfutil "github.com/devfile/library/pkg/util"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/preference"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
const (
@@ -31,7 +33,7 @@ func NewFlagsBackend(preferenceClient preference.Client) *FlagsBackend {
}
}
func (o *FlagsBackend) Validate(flags map[string]string) error {
func (o *FlagsBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
if flags[FLAG_NAME] == "" {
return errors.New("missing --name parameter: please add --name <name> to specify a name for the component")
}
@@ -54,10 +56,19 @@ func (o *FlagsBackend) Validate(flags map[string]string) error {
if err != nil {
return err
}
empty, err := location.DirIsEmpty(fs, dir)
if err != nil {
return err
}
if !empty && flags[FLAG_STARTER] != "" {
return errors.New("--starter parameter cannot be used when the directory is not empty")
}
return nil
}
func (o *FlagsBackend) SelectDevfile(flags map[string]string) (*DevfileLocation, error) {
func (o *FlagsBackend) SelectDevfile(flags map[string]string, _ filesystem.Filesystem, _ string) (*DevfileLocation, error) {
return &DevfileLocation{
Devfile: flags[FLAG_DEVFILE],
DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],

View File

@@ -10,9 +10,10 @@ import (
"github.com/devfile/library/pkg/devfile/parser"
parsercontext "github.com/devfile/library/pkg/devfile/parser/context"
"github.com/devfile/library/pkg/devfile/parser/data"
"github.com/devfile/library/pkg/testingutil/filesystem"
dffilesystem "github.com/devfile/library/pkg/testingutil/filesystem"
"github.com/redhat-developer/odo/pkg/preference"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
func TestFlagsBackend_SelectDevfile(t *testing.T) {
@@ -45,7 +46,7 @@ func TestFlagsBackend_SelectDevfile(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &FlagsBackend{}
got, err := o.SelectDevfile(tt.fields.flags)
got, err := o.SelectDevfile(tt.fields.flags, nil, "")
if (err != nil) != tt.wantErr {
t.Errorf("FlagsBackend.SelectDevfile() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -62,6 +63,8 @@ func TestFlagsBackend_Validate(t *testing.T) {
}
type args struct {
flags map[string]string
fsys func() filesystem.Filesystem
dir string
}
tests := []struct {
name string
@@ -77,6 +80,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
flags: map[string]string{
"name": "",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
wantErr: true,
},
@@ -86,6 +95,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
flags: map[string]string{
"name": "aname",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
wantErr: true,
},
@@ -96,6 +111,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
"name": "aname",
"devfile": "adevfile",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
registryList: []preference.Registry{
{
@@ -113,6 +134,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
"devfile": "adevfile",
"devfile-path": "apath",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
wantErr: true,
},
@@ -124,6 +151,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
"devfile": "adevfile",
"devfile-registry": "aregistry",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
registryNameExists: true,
wantErr: false,
@@ -136,6 +169,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
"devfile": "adevfile",
"devfile-registry": "aregistry",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
registryNameExists: false,
wantErr: true,
@@ -148,6 +187,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
"devfile-path": "apath",
"devfile-registry": "aregistry",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
registryNameExists: true,
wantErr: true,
@@ -159,6 +204,12 @@ func TestFlagsBackend_Validate(t *testing.T) {
"name": "1234",
"devfile": "adevfile",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
wantErr: true,
},
@@ -169,6 +220,47 @@ func TestFlagsBackend_Validate(t *testing.T) {
"name": "WrongName",
"devfile": "adevfile",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
wantErr: true,
},
{
name: "starter flag with an empty directory",
args: args{
flags: map[string]string{
"name": "aname",
"devfile": "adevfile",
"starter": "astarter",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
return fs
},
dir: "/tmp",
},
wantErr: false,
},
{
name: "starter flag with a non empty directory",
args: args{
flags: map[string]string{
"name": "aname",
"devfile": "adevfile",
"starter": "astarter",
},
fsys: func() filesystem.Filesystem {
fs := filesystem.NewFakeFs()
_ = fs.MkdirAll("/tmp", 0644)
_ = fs.WriteFile("/tmp/main.go", []byte("package main"), 0644)
return fs
},
dir: "/tmp",
},
wantErr: true,
},
@@ -184,7 +276,7 @@ func TestFlagsBackend_Validate(t *testing.T) {
o := &FlagsBackend{
preferenceClient: prefClient,
}
if err := o.Validate(tt.args.flags); (err != nil) != tt.wantErr {
if err := o.Validate(tt.args.flags, tt.args.fsys(), tt.args.dir); (err != nil) != tt.wantErr {
t.Errorf("FlagsBackend.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
@@ -258,9 +350,6 @@ func TestFlagsBackend_SelectStarterProject(t *testing.T) {
{
Name: "starter1",
},
{
Name: "starter2",
},
{
Name: "starter3",
},
@@ -301,7 +390,7 @@ func TestFlagsBackend_PersonalizeName(t *testing.T) {
preferenceClient preference.Client
}
type args struct {
devfile func(fs filesystem.Filesystem) parser.DevfileObj
devfile func(fs dffilesystem.Filesystem) parser.DevfileObj
flags map[string]string
}
tests := []struct {
@@ -314,7 +403,7 @@ func TestFlagsBackend_PersonalizeName(t *testing.T) {
{
name: "name flag",
args: args{
devfile: func(fs filesystem.Filesystem) parser.DevfileObj {
devfile: func(fs dffilesystem.Filesystem) parser.DevfileObj {
devfileData, _ := data.NewDevfileData(string(data.APISchemaVersion200))
obj := parser.DevfileObj{
Ctx: parsercontext.FakeContext(fs, "/tmp/devfile.yaml"),
@@ -338,7 +427,7 @@ func TestFlagsBackend_PersonalizeName(t *testing.T) {
o := &FlagsBackend{
preferenceClient: tt.fields.preferenceClient,
}
fs := filesystem.NewFakeFs()
fs := dffilesystem.NewFakeFs()
devfile := tt.args.devfile(fs)
err := o.PersonalizeName(devfile, tt.args.flags)
if (err != nil) != tt.wantErr {

View File

@@ -9,6 +9,7 @@ import (
"github.com/redhat-developer/odo/pkg/catalog"
"github.com/redhat-developer/odo/pkg/init/asker"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
const (
@@ -19,22 +20,22 @@ const (
// InteractiveBackend is a backend that will ask information interactively using the `asker` package
type InteractiveBackend struct {
asker asker.Asker
askerClient asker.Asker
catalogClient catalog.Client
}
func NewInteractiveBackend(asker asker.Asker, catalogClient catalog.Client) *InteractiveBackend {
func NewInteractiveBackend(askerClient asker.Asker, catalogClient catalog.Client) *InteractiveBackend {
return &InteractiveBackend{
asker: asker,
askerClient: askerClient,
catalogClient: catalogClient,
}
}
func (o *InteractiveBackend) Validate(flags map[string]string) error {
func (o *InteractiveBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
return nil
}
func (o *InteractiveBackend) SelectDevfile(flags map[string]string) (*DevfileLocation, error) {
func (o *InteractiveBackend) SelectDevfile(flags map[string]string, _ filesystem.Filesystem, _ string) (*DevfileLocation, error) {
result := &DevfileLocation{}
devfileEntries, _ := o.catalogClient.ListDevfileComponents("")
@@ -48,7 +49,7 @@ loop:
switch state {
case STATE_ASK_LANG:
lang, err = o.asker.AskLanguage(langs)
lang, err = o.askerClient.AskLanguage(langs)
if err != nil {
return nil, err
}
@@ -57,7 +58,7 @@ loop:
case STATE_ASK_TYPE:
types := devfileEntries.GetProjectTypes(lang)
var back bool
back, details, err = o.asker.AskType(types)
back, details, err = o.askerClient.AskType(types)
if err != nil {
return nil, err
}
@@ -86,7 +87,7 @@ func (o *InteractiveBackend) SelectStarterProject(devfile parser.DevfileObj, fla
names = append(names, starterProject.Name)
}
ok, starter, err := o.asker.AskStarterProject(names)
ok, starter, err := o.askerClient.AskStarterProject(names)
if err != nil {
return nil, err
}
@@ -97,7 +98,7 @@ func (o *InteractiveBackend) SelectStarterProject(devfile parser.DevfileObj, fla
}
func (o *InteractiveBackend) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) error {
name, err := o.asker.AskName(fmt.Sprintf("my-%s-app", devfile.Data.GetMetadata().Name))
name, err := o.askerClient.AskName(fmt.Sprintf("my-%s-app", devfile.Data.GetMetadata().Name))
if err != nil {
return err
}

View File

@@ -84,10 +84,10 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
o := &InteractiveBackend{
asker: tt.fields.buildAsker(ctrl),
askerClient: tt.fields.buildAsker(ctrl),
catalogClient: tt.fields.buildCatalogClient(ctrl),
}
got, err := o.SelectDevfile(map[string]string{})
got, err := o.SelectDevfile(map[string]string{}, nil, "")
if (err != nil) != tt.wantErr {
t.Errorf("InteractiveBuilder.ParamsBuild() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -180,7 +180,7 @@ func TestInteractiveBackend_SelectStarterProject(t *testing.T) {
askerClient = tt.fields.asker(ctrl)
}
o := &InteractiveBackend{
asker: askerClient,
askerClient: askerClient,
catalogClient: tt.fields.catalogClient,
}
got1, err := o.SelectStarterProject(tt.args.devfile(), tt.args.flags)
@@ -245,7 +245,7 @@ func TestInteractiveBackend_PersonalizeName(t *testing.T) {
askerClient = tt.fields.asker(ctrl)
}
o := &InteractiveBackend{
asker: askerClient,
askerClient: askerClient,
catalogClient: tt.fields.catalogClient,
}
fs := filesystem.NewFakeFs()

View File

@@ -6,15 +6,16 @@ package backend
import (
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/parser"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
// InitBackend is a specialized backend for steps of initiating a project, based on various input (either from CLI flags or interactively from user)
type InitBackend interface {
// Validate returns an error if it does not validate the flags
Validate(flags map[string]string) error
// Validate returns an error if it does not validate the flags based on the directory content
Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error
// SelectDevfile selects a devfile and returns its location information, depending on the flags
SelectDevfile(flags map[string]string) (location *DevfileLocation, err error)
SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (location *DevfileLocation, err error)
// SelectStarterProject selects a starter project from the devfile and returns information about the starter project,
// depending on the flags. If not starter project is selected, a nil starter is returned

View File

@@ -10,6 +10,7 @@ import (
v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
parser "github.com/devfile/library/pkg/devfile/parser"
gomock "github.com/golang/mock/gomock"
filesystem "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
// MockInitBackend is a mock of InitBackend interface.
@@ -50,18 +51,18 @@ func (mr *MockInitBackendMockRecorder) PersonalizeName(devfile, flags interface{
}
// SelectDevfile mocks base method.
func (m *MockInitBackend) SelectDevfile(flags map[string]string) (*DevfileLocation, error) {
func (m *MockInitBackend) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*DevfileLocation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SelectDevfile", flags)
ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir)
ret0, _ := ret[0].(*DevfileLocation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SelectDevfile indicates an expected call of SelectDevfile.
func (mr *MockInitBackendMockRecorder) SelectDevfile(flags interface{}) *gomock.Call {
func (mr *MockInitBackendMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockInitBackend)(nil).SelectDevfile), flags)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockInitBackend)(nil).SelectDevfile), flags, fs, dir)
}
// SelectStarterProject mocks base method.
@@ -80,15 +81,15 @@ func (mr *MockInitBackendMockRecorder) SelectStarterProject(devfile, flags inter
}
// Validate mocks base method.
func (m *MockInitBackend) Validate(flags map[string]string) error {
func (m *MockInitBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate", flags)
ret := m.ctrl.Call(m, "Validate", flags, fs, dir)
ret0, _ := ret[0].(error)
return ret0
}
// Validate indicates an expected call of Validate.
func (mr *MockInitBackendMockRecorder) Validate(flags interface{}) *gomock.Call {
func (mr *MockInitBackendMockRecorder) Validate(flags, fs, dir interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockInitBackend)(nil).Validate), flags)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockInitBackend)(nil).Validate), flags, fs, dir)
}

View File

@@ -1,6 +1,7 @@
package init
import (
"errors"
"fmt"
"net/url"
"path/filepath"
@@ -11,6 +12,7 @@ import (
dfutil "github.com/devfile/library/pkg/util"
"github.com/redhat-developer/odo/pkg/catalog"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/init/asker"
"github.com/redhat-developer/odo/pkg/init/backend"
"github.com/redhat-developer/odo/pkg/init/registry"
@@ -24,43 +26,71 @@ type InitClient struct {
// Backends
flagsBackend *backend.FlagsBackend
interactiveBackend *backend.InteractiveBackend
alizerBackend *backend.AlizerBackend
// Clients
fsys filesystem.Filesystem
preferenceClient preference.Client
registryClient registry.Client
catalogClient catalog.Client
}
func NewInitClient(fsys filesystem.Filesystem, preferenceClient preference.Client, registryClient registry.Client) *InitClient {
func NewInitClient(fsys filesystem.Filesystem, preferenceClient preference.Client, registryClient registry.Client, catalogClient catalog.Client) *InitClient {
// We create the asker client and the backends here and not at the CLI level, as we want to hide these details to the CLI
askerClient := asker.NewSurveyAsker()
return &InitClient{
flagsBackend: backend.NewFlagsBackend(preferenceClient),
interactiveBackend: backend.NewInteractiveBackend(asker.NewSurveyAsker(), catalog.NewCatalogClient(fsys, preferenceClient)),
interactiveBackend: backend.NewInteractiveBackend(askerClient, catalogClient),
alizerBackend: backend.NewAlizerBackend(askerClient, catalogClient),
fsys: fsys,
preferenceClient: preferenceClient,
registryClient: registryClient,
catalogClient: catalogClient,
}
}
// Validate calls Validate method of the adequate backend
func (o *InitClient) Validate(flags map[string]string) error {
func (o *InitClient) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
var backend backend.InitBackend
if len(flags) == 0 {
backend = o.interactiveBackend
} else {
backend = o.flagsBackend
}
return backend.Validate(flags)
return backend.Validate(flags, fs, dir)
}
// SelectDevfile calls SelectDevfile methods of the adequate backend
func (o *InitClient) SelectDevfile(flags map[string]string) (*backend.DevfileLocation, error) {
func (o *InitClient) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*backend.DevfileLocation, error) {
var backend backend.InitBackend
if len(flags) == 0 {
empty, err := location.DirIsEmpty(fs, dir)
if err != nil {
return nil, err
}
if empty && len(flags) == 0 {
backend = o.interactiveBackend
} else if len(flags) == 0 {
backend = o.alizerBackend
} else {
backend = o.flagsBackend
}
return backend.SelectDevfile(flags)
location, err := backend.SelectDevfile(flags, fs, dir)
if err != nil {
return nil, err
}
// If Alizer failed to determine the devfile, run interactively
if location == nil {
if backend == o.alizerBackend {
backend = o.interactiveBackend
return backend.SelectDevfile(flags, fs, dir)
} else {
return nil, errors.New("unable to determine the devfile location")
}
}
return location, err
}
func (o *InitClient) DownloadDevfile(devfileLocation *backend.DevfileLocation, destDir string) (string, error) {
@@ -152,10 +182,17 @@ func (o *InitClient) downloadFromRegistry(registryName string, devfile string, d
}
// SelectStarterProject calls SelectStarterProject methods of the adequate backend
func (o *InitClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) {
func (o *InitClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) (*v1alpha2.StarterProject, error) {
var backend backend.InitBackend
if len(flags) == 0 {
onlyDevfile, err := location.DirContainsOnlyDevfile(fs, dir)
if err != nil {
return nil, err
}
if onlyDevfile && len(flags) == 0 {
backend = o.interactiveBackend
} else if len(flags) == 0 {
backend = o.alizerBackend
} else {
backend = o.flagsBackend
}

View File

@@ -12,15 +12,17 @@ import (
"github.com/devfile/library/pkg/devfile/parser"
"github.com/redhat-developer/odo/pkg/init/backend"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
type Client interface {
// Validate checks for each backend if flags are valid
Validate(flags map[string]string) error
Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error
// SelectDevfile returns information about a devfile selected based on the flags, or
// SelectDevfile returns information about a devfile selected based on Alizer if the directory content,
// or based on the flags if the directory is empty, or
// interactively if flags is empty
SelectDevfile(flags map[string]string) (*backend.DevfileLocation, error)
SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*backend.DevfileLocation, error)
// DownloadDevfile downloads a devfile given its location information and a destination directory
// and returns the path of the downloaded file
@@ -28,7 +30,7 @@ type Client interface {
// SelectStarterProject selects a starter project from the devfile and returns information about the starter project,
// depending on the flags. If not starter project is selected, a nil starter is returned
SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error)
SelectStarterProject(devfile parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) (*v1alpha2.StarterProject, error)
// DownloadStarterProject downloads the starter project referenced in devfile and stores it in dest directory
// WARNING: This will first remove all the content of dest.

View File

@@ -11,6 +11,7 @@ import (
parser "github.com/devfile/library/pkg/devfile/parser"
gomock "github.com/golang/mock/gomock"
backend "github.com/redhat-developer/odo/pkg/init/backend"
filesystem "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)
// MockClient is a mock of Client interface.
@@ -80,45 +81,45 @@ func (mr *MockClientMockRecorder) PersonalizeName(devfile, flags interface{}) *g
}
// SelectDevfile mocks base method.
func (m *MockClient) SelectDevfile(flags map[string]string) (*backend.DevfileLocation, error) {
func (m *MockClient) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*backend.DevfileLocation, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SelectDevfile", flags)
ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir)
ret0, _ := ret[0].(*backend.DevfileLocation)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SelectDevfile indicates an expected call of SelectDevfile.
func (mr *MockClientMockRecorder) SelectDevfile(flags interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags, fs, dir)
}
// SelectStarterProject mocks base method.
func (m *MockClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) {
func (m *MockClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) (*v1alpha2.StarterProject, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags)
ret := m.ctrl.Call(m, "SelectStarterProject", devfile, flags, fs, dir)
ret0, _ := ret[0].(*v1alpha2.StarterProject)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SelectStarterProject indicates an expected call of SelectStarterProject.
func (mr *MockClientMockRecorder) SelectStarterProject(devfile, flags interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SelectStarterProject(devfile, flags, fs, dir interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockClient)(nil).SelectStarterProject), devfile, flags)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectStarterProject", reflect.TypeOf((*MockClient)(nil).SelectStarterProject), devfile, flags, fs, dir)
}
// Validate mocks base method.
func (m *MockClient) Validate(flags map[string]string) error {
func (m *MockClient) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Validate", flags)
ret := m.ctrl.Call(m, "Validate", flags, fs, dir)
ret0, _ := ret[0].(error)
return ret0
}
// Validate indicates an expected call of Validate.
func (mr *MockClientMockRecorder) Validate(flags interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) Validate(flags, fs, dir interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validate", reflect.TypeOf((*MockClient)(nil).Validate), flags, fs, dir)
}

View File

@@ -7,11 +7,10 @@ package registry
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
dfutil "github.com/devfile/library/pkg/util"
util "github.com/devfile/library/pkg/util"
library "github.com/devfile/registry-support/registry-library/library"
gomock "github.com/golang/mock/gomock"
)
// MockClient is a mock of Client interface.
@@ -38,7 +37,7 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
}
// DownloadFileInMemory mocks base method.
func (m *MockClient) DownloadFileInMemory(params dfutil.HTTPRequestParams) ([]byte, error) {
func (m *MockClient) DownloadFileInMemory(params util.HTTPRequestParams) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DownloadFileInMemory", params)
ret0, _ := ret[0].([]byte)

View File

@@ -2,19 +2,25 @@ package deploy
import (
"fmt"
"os"
"path/filepath"
"github.com/devfile/library/pkg/devfile"
"github.com/devfile/library/pkg/devfile/parser"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/devfile/adapters"
"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/odo/cli/component"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/utils/pointer"
)
// RecommendedCommandName is the recommended command name
@@ -25,6 +31,9 @@ type DeployOptions struct {
// Context
*genericclioptions.Context
// Clients
clientset *clientset.Clientset
// Flags
contextFlag string
}
@@ -40,10 +49,43 @@ func NewDeployOptions() *DeployOptions {
}
func (o *DeployOptions) SetClientset(clientset *clientset.Clientset) {
o.clientset = clientset
}
// Complete DeployOptions after they've been created
func (o *DeployOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
cwd, err := os.Getwd()
if err != nil {
return err
}
containsDevfile, err := location.DirectoryContainsDevfile(filesystem.DefaultFs{}, cwd)
if err != nil {
return err
}
if !containsDevfile {
devfileLocation, err2 := o.clientset.InitClient.SelectDevfile(map[string]string{}, o.clientset.FS, cwd)
if err2 != nil {
return err2
}
devfilePath, err2 := o.clientset.InitClient.DownloadDevfile(devfileLocation, cwd)
if err2 != nil {
return fmt.Errorf("unable to download devfile: %w", err2)
}
devfileObj, _, err2 := devfile.ParseDevfileAndValidate(parser.ParserArgs{Path: devfilePath, FlattenedDevfile: pointer.BoolPtr(false)})
if err2 != nil {
return fmt.Errorf("unable to download devfile: %w", err2)
}
// Set the name in the devfile and writes the devfile back to the disk
err = o.clientset.InitClient.PersonalizeName(devfileObj, map[string]string{})
if err != nil {
return fmt.Errorf("failed to update the devfile's name: %w", err)
}
}
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
@@ -105,10 +147,10 @@ func NewCmdDeploy(name, fullName string) *cobra.Command {
genericclioptions.GenericRun(o, cmd, args)
},
}
clientset.Add(deployCmd, clientset.INIT)
// Add a defined annotation in order to appear in the help menu
deployCmd.Annotations = map[string]string{"command": "utility"}
deployCmd.Annotations["command"] = "utility"
deployCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
odoutil.AddContextFlag(deployCmd, &o.contextFlag)
return deployCmd
}

View File

@@ -2,15 +2,16 @@ package init
import (
"context"
"errors"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/devfile/library/pkg/devfile"
"github.com/devfile/library/pkg/devfile/parser"
"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/init/backend"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
@@ -18,7 +19,6 @@ import (
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
scontext "github.com/redhat-developer/odo/pkg/segment/context"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/utils/pointer"
@@ -85,30 +85,23 @@ func (o *InitOptions) Complete(cmdline cmdline.Cmdline, args []string) (err erro
return err
}
empty, err := isEmpty(o.clientset.FS, o.contextDir)
if err != nil {
return err
}
if !empty {
return errors.New("The current directory is not empty. You can bootstrap new component only in empty directory.\nIf you have existing code that you want to deploy use `odo deploy` or use `odo dev` command to quickly iterate on your component.")
}
o.flags = cmdline.GetFlags()
return nil
}
func isEmpty(fsys filesystem.Filesystem, path string) (bool, error) {
files, err := fsys.ReadDir(path)
if err != nil {
return false, err
}
return len(files) == 0, nil
}
// Validate validates the InitOptions based on completed values
func (o *InitOptions) Validate() error {
err := o.clientset.InitClient.Validate(o.flags)
devfilePresent, err := location.DirectoryContainsDevfile(o.clientset.FS, o.contextDir)
if err != nil {
return err
}
if devfilePresent {
return errors.New("a devfile already exists in the current directory")
}
err = o.clientset.InitClient.Validate(o.flags, o.clientset.FS, o.contextDir)
if err != nil {
return err
}
@@ -125,21 +118,21 @@ func (o *InitOptions) Run() (err error) {
return
}
if starterDownloaded {
err = fmt.Errorf("%w\nThe command failed after downloading the starter project. By security, the directory is not cleaned up.", err)
err = fmt.Errorf("%w\nthe command failed after downloading the starter project. By security, the directory is not cleaned up", err)
} else {
_ = o.clientset.FS.Remove("devfile.yaml")
err = fmt.Errorf("%w\nThe command failed, the devfile has been removed from current directory.", err)
err = fmt.Errorf("%w\nthe command failed, the devfile has been removed from current directory", err)
}
}()
o.devfileLocation, err = o.clientset.InitClient.SelectDevfile(o.flags)
o.devfileLocation, err = o.clientset.InitClient.SelectDevfile(o.flags, o.clientset.FS, o.contextDir)
if err != nil {
return err
}
devfilePath, err := o.clientset.InitClient.DownloadDevfile(o.devfileLocation, o.contextDir)
if err != nil {
return fmt.Errorf("Unable to download devfile: %w", err)
return fmt.Errorf("unable to download devfile: %w", err)
}
devfileObj, _, err := devfile.ParseDevfileAndValidate(parser.ParserArgs{Path: devfilePath, FlattenedDevfile: pointer.BoolPtr(false)})
@@ -149,7 +142,7 @@ func (o *InitOptions) Run() (err error) {
scontext.SetComponentType(o.ctx, component.GetComponentTypeFromDevfileMetadata(devfileObj.Data.GetMetadata()))
starterInfo, err := o.clientset.InitClient.SelectStarterProject(devfileObj, o.flags)
starterInfo, err := o.clientset.InitClient.SelectStarterProject(devfileObj, o.flags, o.clientset.FS, o.contextDir)
if err != nil {
return err
}

View File

@@ -25,11 +25,12 @@ func TestInitOptions_Complete(t *testing.T) {
name: "directory not empty",
cmdlineExpects: func(mock *cmdline.MockCmdline) {
mock.EXPECT().Context().Return(context.Background())
mock.EXPECT().GetFlags().Times(1)
},
fsysPopulate: func(fsys filesystem.Filesystem) {
_ = fsys.WriteFile(".emptyfile", []byte(""), 0644)
},
wantErr: true,
wantErr: false,
},
{
name: "directory empty",

View File

@@ -48,7 +48,7 @@ const (
// Clients will be created only once and be reused for sub-dependencies
var subdeps map[string][]string = map[string][]string{
CATALOG: {FILESYSTEM, PREFERENCE},
INIT: {FILESYSTEM, PREFERENCE, REGISTRY},
INIT: {FILESYSTEM, PREFERENCE, REGISTRY, CATALOG},
PROJECT: {KUBERNETES_NULLABLE},
/* Add sub-dependencies here, if any */
}
@@ -112,7 +112,7 @@ func Fetch(command *cobra.Command) (*Clientset, error) {
dep.CatalogClient = catalog.NewCatalogClient(dep.FS, dep.PreferenceClient)
}
if isDefined(command, INIT) {
dep.InitClient = _init.NewInitClient(dep.FS, dep.PreferenceClient, dep.RegistryClient)
dep.InitClient = _init.NewInitClient(dep.FS, dep.PreferenceClient, dep.RegistryClient, dep.CatalogClient)
}
if isDefined(command, PROJECT) {
dep.ProjectClient = project.NewClient(dep.KubernetesClient)

View File

@@ -1,6 +1,7 @@
package genericclioptions
import (
"errors"
"fmt"
"github.com/redhat-developer/odo/pkg/devfile"
@@ -133,18 +134,22 @@ func New(parameters CreateParameters) (*Context, error) {
}
ctx.devfilePath = location.DevfileLocation(parameters.componentContext)
isDevfile := odoutil.CheckPathExists(ctx.devfilePath)
if parameters.devfile && isDevfile {
// Parse devfile and validate
devObj, err := devfile.ParseAndValidateFromFile(ctx.devfilePath)
if err != nil {
return nil, fmt.Errorf("failed to parse the devfile %s, with error: %s", ctx.devfilePath, err)
if parameters.devfile {
isDevfile := odoutil.CheckPathExists(ctx.devfilePath)
if isDevfile {
// Parse devfile and validate
devObj, err := devfile.ParseAndValidateFromFile(ctx.devfilePath)
if err != nil {
return nil, fmt.Errorf("failed to parse the devfile %s, with error: %s", ctx.devfilePath, err)
}
err = validate.ValidateDevfileData(devObj.Data)
if err != nil {
return nil, err
}
ctx.EnvSpecificInfo.SetDevfileObj(devObj)
} else {
return nil, errors.New("no devfile found")
}
err = validate.ValidateDevfileData(devObj.Data)
if err != nil {
return nil, err
}
ctx.EnvSpecificInfo.SetDevfileObj(devObj)
}
return &Context{

View File

@@ -241,7 +241,7 @@ func TestNew(t *testing.T) {
_ = fs.WriteFile(filepath.Join(prefixDir, "myapp", ".odo", "env", "env.yaml"), []byte{}, 0644)
},
},
expectedErr: "",
expectedErr: "no devfile found",
expected: Context{
internalCxt: internalCxt{
project: "myproject",

View File

@@ -187,20 +187,6 @@ func (mr *MockClientMockRecorder) NewPreferenceList() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewPreferenceList", reflect.TypeOf((*MockClient)(nil).NewPreferenceList))
}
// RegistryCacheTime mocks base method.
func (m *MockClient) RegistryCacheTime() *int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RegistryCacheTime")
ret0, _ := ret[0].(*int)
return ret0
}
// RegistryCacheTime indicates an expected call of RegistryCacheTime.
func (mr *MockClientMockRecorder) RegistryCacheTime() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegistryCacheTime", reflect.TypeOf((*MockClient)(nil).RegistryCacheTime))
}
// PushTimeout mocks base method.
func (m *MockClient) PushTimeout() *int {
m.ctrl.T.Helper()
@@ -215,6 +201,20 @@ func (mr *MockClientMockRecorder) PushTimeout() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushTimeout", reflect.TypeOf((*MockClient)(nil).PushTimeout))
}
// RegistryCacheTime mocks base method.
func (m *MockClient) RegistryCacheTime() *int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RegistryCacheTime")
ret0, _ := ret[0].(*int)
return ret0
}
// RegistryCacheTime indicates an expected call of RegistryCacheTime.
func (mr *MockClientMockRecorder) RegistryCacheTime() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegistryCacheTime", reflect.TypeOf((*MockClient)(nil).RegistryCacheTime))
}
// RegistryHandler mocks base method.
func (m *MockClient) RegistryHandler(operation, registryName, registryURL string, forceFlag, isSecure bool) error {
m.ctrl.T.Helper()

View File

@@ -33,6 +33,16 @@ var _ = Describe("odo devfile init command tests", func() {
files := helper.ListFilesInDir(commonVar.Context)
Expect(len(files)).To(Equal(0))
})
By("running odo init in a directory containing a devfile.yaml", func() {
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-registry.yaml"), filepath.Join(commonVar.Context, "devfile.yaml"))
err := helper.Cmd("odo", "init").ShouldFail().Err()
Expect(err).To(ContainSubstring("a devfile already exists in the current directory"))
})
By("running odo init in a directory containing a .devfile.yaml", func() {
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-registry.yaml"), filepath.Join(commonVar.Context, ".devfile.yaml"))
err := helper.Cmd("odo", "init").ShouldFail().Err()
Expect(err).To(ContainSubstring("a devfile already exists in the current directory"))
})
})
When("running odo init with valid flags", func() {
@@ -46,6 +56,19 @@ var _ = Describe("odo devfile init command tests", func() {
})
})
When("running odo init from a directory with sources", func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
})
It("should work without --starter flag", func() {
helper.Cmd("odo", "init", "--name", "aname", "--devfile", "nodejs").ShouldPass()
})
It("should not accept --starter flag", func() {
err := helper.Cmd("odo", "init", "--name", "aname", "--devfile", "nodejs", "--starter", "nodejs-starter").ShouldFail().Err()
Expect(err).To(ContainSubstring("--starter parameter cannot be used when the directory is not empty"))
})
})
When("devfile contains parent URI", func() {
var originalKeyList []string
var srcDevfile string

277
vendor/github.com/redhat-developer/alizer/go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,277 @@
Eclipse Public License - v 2.0
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial content
Distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from
and are Distributed by that particular Contributor. A Contribution
"originates" from a Contributor if it was added to the Program by
such Contributor itself or anyone acting on such Contributor's behalf.
Contributions do not include changes or additions to the Program that
are not Modified Works.
"Contributor" means any person or entity that Distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which
are necessarily infringed by the use or sale of its Contribution alone
or when combined with the Program.
"Program" means the Contributions Distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement
or any Secondary License (as applicable), including Contributors.
"Derivative Works" shall mean any work, whether in Source Code or other
form, that is based on (or derived from) the Program and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship.
"Modified Works" shall mean any work in Source Code or other form that
results from an addition to, deletion from, or modification of the
contents of the Program, including, for purposes of clarity any new file
in Source Code form that contains any contents of the Program. Modified
Works shall not include works that contain only declarations,
interfaces, types, classes, structures, or files of the Program solely
in each case in order to link to, bind by name, or subclass the Program
or Modified Works thereof.
"Distribute" means the acts of a) distributing or b) making available
in any manner that enables the transfer of a copy.
"Source Code" means the form of a Program preferred for making
modifications, including but not limited to software source code,
documentation source, and configuration files.
"Secondary License" means either the GNU General Public License,
Version 2.0, or any later versions of that license, including any
exceptions or additional permissions as identified by the initial
Contributor.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby
grants Recipient a non-exclusive, worldwide, royalty-free copyright
license to reproduce, prepare Derivative Works of, publicly display,
publicly perform, Distribute and sublicense the Contribution of such
Contributor, if any, and such Derivative Works.
b) Subject to the terms of this Agreement, each Contributor hereby
grants Recipient a non-exclusive, worldwide, royalty-free patent
license under Licensed Patents to make, use, sell, offer to sell,
import and otherwise transfer the Contribution of such Contributor,
if any, in Source Code or other form. This patent license shall
apply to the combination of the Contribution and the Program if, at
the time the Contribution is added by the Contributor, such addition
of the Contribution causes such combination to be covered by the
Licensed Patents. The patent license shall not apply to any other
combinations which include the Contribution. No hardware per se is
licensed hereunder.
c) Recipient understands that although each Contributor grants the
licenses to its Contributions set forth herein, no assurances are
provided by any Contributor that the Program does not infringe the
patent or other intellectual property rights of any other entity.
Each Contributor disclaims any liability to Recipient for claims
brought by any other entity based on infringement of intellectual
property rights or otherwise. As a condition to exercising the
rights and licenses granted hereunder, each Recipient hereby
assumes sole responsibility to secure any other intellectual
property rights needed, if any. For example, if a third party
patent license is required to allow Recipient to Distribute the
Program, it is Recipient's responsibility to acquire that license
before distributing the Program.
d) Each Contributor represents that to its knowledge it has
sufficient copyright rights in its Contribution, if any, to grant
the copyright license set forth in this Agreement.
e) Notwithstanding the terms of any Secondary License, no
Contributor makes additional grants to any Recipient (other than
those set forth in this Agreement) as a result of such Recipient's
receipt of the Program under the terms of a Secondary License
(if permitted under the terms of Section 3).
3. REQUIREMENTS
3.1 If a Contributor Distributes the Program in any form, then:
a) the Program must also be made available as Source Code, in
accordance with section 3.2, and the Contributor must accompany
the Program with a statement that the Source Code for the Program
is available under this Agreement, and informs Recipients how to
obtain it in a reasonable manner on or through a medium customarily
used for software exchange; and
b) the Contributor may Distribute the Program under a license
different than this Agreement, provided that such license:
i) effectively disclaims on behalf of all other Contributors all
warranties and conditions, express and implied, including
warranties or conditions of title and non-infringement, and
implied warranties or conditions of merchantability and fitness
for a particular purpose;
ii) effectively excludes on behalf of all other Contributors all
liability for damages, including direct, indirect, special,
incidental and consequential damages, such as lost profits;
iii) does not attempt to limit or alter the recipients' rights
in the Source Code under section 3.2; and
iv) requires any subsequent distribution of the Program by any
party to be under a license that satisfies the requirements
of this section 3.
3.2 When the Program is Distributed as Source Code:
a) it must be made available under this Agreement, or if the
Program (i) is combined with other material in a separate file or
files made available under a Secondary License, and (ii) the initial
Contributor attached to the Source Code the notice described in
Exhibit A of this Agreement, then the Program may be made available
under the terms of such Secondary Licenses, and
b) a copy of this Agreement must be included with each copy of
the Program.
3.3 Contributors may not remove or alter any copyright, patent,
trademark, attribution notices, disclaimers of warranty, or limitations
of liability ("notices") contained within the Program from any copy of
the Program which they Distribute, provided that Contributors may add
their own appropriate notices.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities
with respect to end users, business partners and the like. While this
license is intended to facilitate the commercial use of the Program,
the Contributor who includes the Program in a commercial product
offering should do so in a manner which does not create potential
liability for other Contributors. Therefore, if a Contributor includes
the Program in a commercial product offering, such Contributor
("Commercial Contributor") hereby agrees to defend and indemnify every
other Contributor ("Indemnified Contributor") against any losses,
damages and costs (collectively "Losses") arising from claims, lawsuits
and other legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such
Commercial Contributor in connection with its distribution of the Program
in a commercial product offering. The obligations in this section do not
apply to any claims or Losses relating to any actual or alleged
intellectual property infringement. In order to qualify, an Indemnified
Contributor must: a) promptly notify the Commercial Contributor in
writing of such claim, and b) allow the Commercial Contributor to control,
and cooperate with the Commercial Contributor in, the defense and any
related settlement negotiations. The Indemnified Contributor may
participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial
product offering, Product X. That Contributor is then a Commercial
Contributor. If that Commercial Contributor then makes performance
claims, or offers warranties related to Product X, those performance
claims and warranties are such Commercial Contributor's responsibility
alone. Under this section, the Commercial Contributor would have to
defend claims against the other Contributors related to those performance
claims and warranties, and if a court requires any other Contributor to
pay any damages as a result, the Commercial Contributor must pay
those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
PURPOSE. Each Recipient is solely responsible for determining the
appropriateness of using and distributing the Program and assumes all
risks associated with its exercise of rights under this Agreement,
including but not limited to the risks and costs of program errors,
compliance with applicable laws, damage to or loss of data, programs
or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this Agreement, and without further
action by the parties hereto, such provision shall be reformed to the
minimum extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity
(including a cross-claim or counterclaim in a lawsuit) alleging that the
Program itself (excluding combinations of the Program with other software
or hardware) infringes such Recipient's patent(s), then such Recipient's
rights granted under Section 2(b) shall terminate as of the date such
litigation is filed.
All Recipient's rights under this Agreement shall terminate if it
fails to comply with any of the material terms or conditions of this
Agreement and does not cure such failure in a reasonable period of
time after becoming aware of such noncompliance. If all Recipient's
rights under this Agreement terminate, Recipient agrees to cease use
and distribution of the Program as soon as reasonably practicable.
However, Recipient's obligations under this Agreement and any licenses
granted by Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement,
but in order to avoid inconsistency the Agreement is copyrighted and
may only be modified in the following manner. The Agreement Steward
reserves the right to publish new versions (including revisions) of
this Agreement from time to time. No one other than the Agreement
Steward has the right to modify this Agreement. The Eclipse Foundation
is the initial Agreement Steward. The Eclipse Foundation may assign the
responsibility to serve as the Agreement Steward to a suitable separate
entity. Each new version of the Agreement will be given a distinguishing
version number. The Program (including Contributions) may always be
Distributed subject to the version of the Agreement under which it was
received. In addition, after a new version of the Agreement is published,
Contributor may elect to Distribute the Program (including its
Contributions) under the new version.
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
receives no rights or licenses to the intellectual property of any
Contributor under this Agreement, whether expressly, by implication,
estoppel or otherwise. All rights in the Program not expressly granted
under this Agreement are reserved. Nothing in this Agreement is intended
to be enforceable by any entity that is not a Contributor or Recipient.
No third-party beneficiary rights are created under this Agreement.
Exhibit A - Form of Secondary Licenses Notice
"This Source Code may also be made available under the following
Secondary Licenses when the conditions for such availability set forth
in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
version(s), and exceptions or additional permissions here}."
Simply including a copy of this Agreement, including this Exhibit A
is not sufficient to license the Source Code under Secondary Licenses.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to
look for such a notice.
You may add additional accurate notices of copyright ownership.

View File

@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
framework "github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/dotnet"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
utils "github.com/redhat-developer/alizer/go/pkg/utils"
)
type DotNetEnricher struct{}
func (j DotNetEnricher) GetSupportedLanguages() []string {
return []string{"c#", "f#", "visual basic .net"}
}
func getDotNetFrameworkDetectors() []FrameworkDetectorWithConfigFile {
return []FrameworkDetectorWithConfigFile{
&framework.DotNetDetector{},
}
}
func (j DotNetEnricher) DoEnrichLanguage(language *language.Language, files *[]string) {
configFiles := utils.GetFilesByRegex(files, ".*\\.\\w+proj")
for _, configFile := range configFiles {
getDotNetFrameworks(language, configFile)
}
}
func (j DotNetEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
return IsConfigurationValidForLanguage(language, config)
}
func getDotNetFrameworks(language *language.Language, configFile string) {
for _, detector := range getDotNetFrameworkDetectors() {
detector.DoFrameworkDetection(language, configFile)
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"github.com/redhat-developer/alizer/go/pkg/utils/langfiles"
)
type Enricher interface {
GetSupportedLanguages() []string
DoEnrichLanguage(language *language.Language, files *[]string)
IsConfigValidForComponentDetection(language string, configFile string) bool
}
type FrameworkDetectorWithConfigFile interface {
DoFrameworkDetection(language *language.Language, config string)
}
type FrameworkDetectorWithoutConfigFile interface {
DoFrameworkDetection(language *language.Language, files *[]string)
}
/*
IsConfigurationValidForLanguage check whether the configuration file is valid for current language.
For example when analyzing a nodejs project, we could find a package.json
within the node_modules folder. That is not to be considered valid
for component detection.
Paramenters:
language: language name
file: configuration file name
Returns:
bool: true if config file is valid for current language
*/
func IsConfigurationValidForLanguage(language string, file string) bool {
languageItem, err := langfiles.Get().GetLanguageByName(language)
if err != nil {
return false
}
for _, excludeFolder := range languageItem.ExcludeFolders {
if isFolderNameIncludedInPath(file, excludeFolder) {
return false
}
}
return true
}
/*
isFolderNameIncludedInPath check if fullpath contains potentialSubFolderName
Parameters:
fullPath: complete path of a file
potentialSubFolderName: folder name
Returns:
bool: true if potentialSubFolderName is included in fullPath
*/
func isFolderNameIncludedInPath(fullPath string, potentialSubFolderName string) bool {
pathSeparator := fmt.Sprintf("%c", os.PathSeparator)
dir, _ := filepath.Split(fullPath)
subDirectories := strings.Split(dir, pathSeparator)
for _, subDir := range subDirectories {
if strings.EqualFold(subDir, potentialSubFolderName) {
return true
}
}
return false
}
func getEnrichers() []Enricher {
return []Enricher{
&JavaEnricher{},
&JavaScriptEnricher{},
&PythonEnricher{},
&DotNetEnricher{},
&GoEnricher{},
}
}
func GetEnricherByLanguage(language string) Enricher {
for _, enricher := range getEnrichers() {
if isLanguageSupportedByEnricher(language, enricher) {
return enricher
}
}
return nil
}
func isLanguageSupportedByEnricher(nameLanguage string, enricher Enricher) bool {
for _, language := range enricher.GetSupportedLanguages() {
if strings.EqualFold(language, nameLanguage) {
return true
}
}
return false
}

View File

@@ -0,0 +1,64 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"encoding/xml"
"io/ioutil"
"os"
"strings"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"github.com/redhat-developer/alizer/go/pkg/schema"
"github.com/redhat-developer/alizer/go/pkg/utils"
)
type DotNetDetector struct{}
func (m DotNetDetector) DoFrameworkDetection(language *language.Language, configFilePath string) {
framework := getFrameworks(configFilePath)
if framework == "" {
return
}
var frameworks []string
if strings.Contains(framework, ";") {
frameworks = strings.Split(framework, ";")
} else {
frameworks = []string{framework}
}
for _, frm := range frameworks {
if !utils.Contains(language.Frameworks, frm) {
language.Frameworks = append(language.Frameworks, frm)
}
}
}
func getFrameworks(configFilePath string) string {
xmlFile, err := os.Open(configFilePath)
if err != nil {
return ""
}
byteValue, _ := ioutil.ReadAll(xmlFile)
var proj schema.DotNetProject
xml.Unmarshal(byteValue, &proj)
defer xmlFile.Close()
if proj.PropertyGroup.TargetFramework != "" {
return proj.PropertyGroup.TargetFramework
} else if proj.PropertyGroup.TargetFrameworkVersion != "" {
return proj.PropertyGroup.TargetFrameworkVersion
} else if proj.PropertyGroup.TargetFrameworks != "" {
return proj.PropertyGroup.TargetFrameworks
}
return ""
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"golang.org/x/mod/modfile"
)
type BeegoDetector struct{}
func (e BeegoDetector) DoFrameworkDetection(language *language.Language, goMod *modfile.File) {
if hasFramework(goMod.Require, "github.com/beego/beego") {
language.Frameworks = append(language.Frameworks, "Beego")
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"golang.org/x/mod/modfile"
)
type EchoDetector struct{}
func (e EchoDetector) DoFrameworkDetection(language *language.Language, goMod *modfile.File) {
if hasFramework(goMod.Require, "github.com/labstack/echo") {
language.Frameworks = append(language.Frameworks, "Echo")
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"golang.org/x/mod/modfile"
)
type FastHttpDetector struct{}
func (e FastHttpDetector) DoFrameworkDetection(language *language.Language, goMod *modfile.File) {
if hasFramework(goMod.Require, "github.com/valyala/fasthttp") {
language.Frameworks = append(language.Frameworks, "FastHttp")
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"golang.org/x/mod/modfile"
)
type GinDetector struct{}
func (e GinDetector) DoFrameworkDetection(language *language.Language, goMod *modfile.File) {
if hasFramework(goMod.Require, "github.com/gin-gonic/gin") {
language.Frameworks = append(language.Frameworks, "Gin")
}
}

View File

@@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"strings"
"golang.org/x/mod/modfile"
)
func hasFramework(modules []*modfile.Require, tag string) bool {
for _, module := range modules {
if strings.EqualFold(module.Mod.Path, tag) || strings.HasPrefix(module.Mod.Path, tag) {
return true
}
}
return false
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"golang.org/x/mod/modfile"
)
type GoFiberDetector struct{}
func (e GoFiberDetector) DoFrameworkDetection(language *language.Language, goMod *modfile.File) {
if hasFramework(goMod.Require, "github.com/gofiber/fiber") {
language.Frameworks = append(language.Frameworks, "GoFiber")
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"golang.org/x/mod/modfile"
)
type MuxDetector struct{}
func (e MuxDetector) DoFrameworkDetection(language *language.Language, goMod *modfile.File) {
if hasFramework(goMod.Require, "github.com/gorilla/mux") {
language.Frameworks = append(language.Frameworks, "Mux")
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
utils "github.com/redhat-developer/alizer/go/pkg/utils"
)
func hasFramework(configFile string, tag string) (bool, error) {
if utils.IsPathOfWantedFile(configFile, "build.gradle") {
return utils.IsTagInFile(configFile, tag)
} else {
return utils.IsTagInPomXMLFile(configFile, tag)
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type MicronautDetector struct{}
func (m MicronautDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFwk, _ := hasFramework(config, "io.micronaut"); hasFwk {
language.Frameworks = append(language.Frameworks, "Micronaut")
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type OpenLibertyDetector struct{}
func (o OpenLibertyDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFwk, _ := hasFramework(config, "io.openliberty"); hasFwk {
language.Frameworks = append(language.Frameworks, "OpenLiberty")
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type QuarkusDetector struct{}
func (q QuarkusDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFwk, _ := hasFramework(config, "io.quarkus"); hasFwk {
language.Frameworks = append(language.Frameworks, "Quarkus")
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type SpringDetector struct{}
func (s SpringDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFwk, _ := hasFramework(config, "org.springframework"); hasFwk {
language.Frameworks = append(language.Frameworks, "Spring")
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type VertxDetector struct{}
func (v VertxDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFwk, _ := hasFramework(config, "io.vertx"); hasFwk {
language.Frameworks = append(language.Frameworks, "Vertx")
}
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type ExpressDetector struct{}
func (e ExpressDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFramework(config, "express") {
language.Frameworks = append(language.Frameworks, "Express")
}
}

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
utils "github.com/redhat-developer/alizer/go/pkg/utils"
)
func hasFramework(configFile string, tag string) bool {
return utils.IsTagInPackageJsonFile(configFile, tag)
}

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type ReactJsDetector struct{}
func (r ReactJsDetector) DoFrameworkDetection(language *language.Language, config string) {
if hasFramework(config, "react") {
language.Frameworks = append(language.Frameworks, "React")
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"github.com/redhat-developer/alizer/go/pkg/utils"
)
type DjangoDetector struct{}
func (d DjangoDetector) DoFrameworkDetection(language *language.Language, files *[]string) {
managePy := utils.GetFile(files, "manage.py")
urlsPy := utils.GetFile(files, "urls.py")
wsgiPy := utils.GetFile(files, "wsgi.py")
asgiPy := utils.GetFile(files, "asgi.py")
djangoFiles := []string{}
utils.AddToArrayIfValueExist(&djangoFiles, managePy)
utils.AddToArrayIfValueExist(&djangoFiles, urlsPy)
utils.AddToArrayIfValueExist(&djangoFiles, wsgiPy)
utils.AddToArrayIfValueExist(&djangoFiles, asgiPy)
if hasFramework(&djangoFiles, "from django.") {
language.Frameworks = append(language.Frameworks, "Django")
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
utils "github.com/redhat-developer/alizer/go/pkg/utils"
)
func hasFramework(files *[]string, tag string) bool {
for _, file := range *files {
if hasTag, _ := utils.IsTagInFile(file, tag); hasTag {
return true
}
}
return false
}

View File

@@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"errors"
"io/ioutil"
framework "github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/go"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
utils "github.com/redhat-developer/alizer/go/pkg/utils"
"golang.org/x/mod/modfile"
)
type GoEnricher struct{}
type GoFrameworkDetector interface {
DoFrameworkDetection(language *language.Language, goMod *modfile.File)
}
func getGoFrameworkDetectors() []GoFrameworkDetector {
return []GoFrameworkDetector{
&framework.GinDetector{},
&framework.BeegoDetector{},
&framework.EchoDetector{},
&framework.FastHttpDetector{},
&framework.GoFiberDetector{},
&framework.MuxDetector{},
}
}
func (j GoEnricher) GetSupportedLanguages() []string {
return []string{"go"}
}
func (j GoEnricher) DoEnrichLanguage(language *language.Language, files *[]string) {
goModPath := utils.GetFile(files, "go.mod")
if goModPath != "" {
goModFile, err := getGoModFile(goModPath)
if err != nil {
return
}
language.Tools = []string{goModFile.Go.Version}
detectGoFrameworks(language, goModFile)
}
}
func (j GoEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
return IsConfigurationValidForLanguage(language, config)
}
func getGoModFile(filePath string) (*modfile.File, error) {
b, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, errors.New("unable to read go.mod file")
}
return modfile.Parse(filePath, b, nil)
}
func detectGoFrameworks(language *language.Language, configFile *modfile.File) {
for _, detector := range getGoFrameworkDetectors() {
detector.DoFrameworkDetection(language, configFile)
}
}

View File

@@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"path/filepath"
"strings"
framework "github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/java"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
utils "github.com/redhat-developer/alizer/go/pkg/utils"
)
type JavaEnricher struct{}
func getJavaFrameworkDetectors() []FrameworkDetectorWithConfigFile {
return []FrameworkDetectorWithConfigFile{
&framework.MicronautDetector{},
&framework.OpenLibertyDetector{},
&framework.QuarkusDetector{},
&framework.SpringDetector{},
&framework.VertxDetector{},
}
}
func (j JavaEnricher) GetSupportedLanguages() []string {
return []string{"java"}
}
func (j JavaEnricher) DoEnrichLanguage(language *language.Language, files *[]string) {
gradle := utils.GetFile(files, "build.gradle")
maven := utils.GetFile(files, "pom.xml")
ant := utils.GetFile(files, "build.xml")
if gradle != "" {
language.Tools = []string{"Gradle"}
detectJavaFrameworks(language, gradle)
} else if maven != "" {
language.Tools = []string{"Maven"}
detectJavaFrameworks(language, maven)
} else if ant != "" {
language.Tools = []string{"Ant"}
}
}
func (j JavaEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
return IsConfigurationValidForLanguage(language, config) && !isParentModuleMaven(config)
}
/*
isParentModuleMaven checks if configuration file is a parent pom.xml
Parameters:
configPath: configuration file path
Returns:
bool: true if config file is parent
*/
func isParentModuleMaven(configPath string) bool {
_, file := filepath.Split(configPath)
if !strings.EqualFold(file, "pom.xml") {
return false
}
hasTag, _ := utils.IsTagInPomXMLFile(configPath, "modules")
return hasTag
}
func detectJavaFrameworks(language *language.Language, configFile string) {
for _, detector := range getJavaFrameworkDetectors() {
detector.DoFrameworkDetection(language, configFile)
}
}

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
framework "github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/javascript/nodejs"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
utils "github.com/redhat-developer/alizer/go/pkg/utils"
)
type JavaScriptEnricher struct{}
func getJavaScriptFrameworkDetectors() []FrameworkDetectorWithConfigFile {
return []FrameworkDetectorWithConfigFile{
&framework.ExpressDetector{},
&framework.ReactJsDetector{},
}
}
func (j JavaScriptEnricher) GetSupportedLanguages() []string {
return []string{"javascript", "typescript"}
}
func (j JavaScriptEnricher) DoEnrichLanguage(language *language.Language, files *[]string) {
packageJson := utils.GetFile(files, "package.json")
if packageJson != "" {
language.Tools = []string{"NodeJs"}
detectJavaScriptFrameworks(language, packageJson)
}
}
func (j JavaScriptEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
return IsConfigurationValidForLanguage(language, config)
}
func detectJavaScriptFrameworks(language *language.Language, configFile string) {
for _, detector := range getJavaScriptFrameworkDetectors() {
detector.DoFrameworkDetection(language, configFile)
}
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
framework "github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/python"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type PythonEnricher struct{}
func getPythonFrameworkDetectors() []FrameworkDetectorWithoutConfigFile {
return []FrameworkDetectorWithoutConfigFile{
&framework.DjangoDetector{},
}
}
func (p PythonEnricher) GetSupportedLanguages() []string {
return []string{"python"}
}
func (p PythonEnricher) DoEnrichLanguage(language *language.Language, files *[]string) {
language.Tools = []string{}
detectPythonFrameworks(language, files)
}
func (p PythonEnricher) IsConfigValidForComponentDetection(language string, config string) bool {
return IsConfigurationValidForLanguage(language, config)
}
func detectPythonFrameworks(language *language.Language, files *[]string) {
for _, detector := range getPythonFrameworkDetectors() {
detector.DoFrameworkDetection(language, files)
}
}

View File

@@ -0,0 +1,20 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package language
type Language struct {
Name string
Aliases []string
UsageInPercentage float64
Frameworks []string
Tools []string
CanBeComponent bool
}

View File

@@ -0,0 +1,248 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"io/fs"
"path/filepath"
"strings"
enricher "github.com/redhat-developer/alizer/go/pkg/apis/enricher"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
"github.com/redhat-developer/alizer/go/pkg/utils/langfiles"
)
type Component struct {
Path string
Languages []language.Language
}
func DetectComponents(path string) ([]Component, error) {
files, err := getFilePaths(path)
if err != nil {
return []Component{}, err
}
components, err := detectComponents(files)
if err != nil {
return []Component{}, err
}
// it may happen that a language has no a specific configuration file (e.g opposite to JAVA -> pom.xml and Nodejs -> package.json)
// we then rely on the language recognizer
directoriesNotBelongingToExistingComponent := getDirectoriesWithoutConfigFile(path, components)
components = append(components, getComponentsWithoutConfigFile(directoriesNotBelongingToExistingComponent)...)
return components, nil
}
/*
getComponentsWithoutConfigFile retrieves the components which are written with a language that does not require a config file
Parameters:
directories: list of directories to analyze
Returns:
components found
*/
func getComponentsWithoutConfigFile(directories []string) []Component {
var components []Component
for _, dir := range directories {
component, _ := detectComponent(dir, "")
if component.Path != "" && isLangForNoConfigComponent(component.Languages) {
components = append(components, component)
}
}
return components
}
/*
isLangForNoConfigComponent verify if main language requires any config file
Parameters:
component:
Returns:
bool: true if language does not require any config file
*/
func isLangForNoConfigComponent(languages []language.Language) bool {
if len(languages) == 0 {
return false
}
lang, err := langfiles.Get().GetLanguageByNameOrAlias(languages[0].Name)
if err != nil {
return false
}
return len(lang.ConfigurationFiles) == 0
}
/*
getDirectoriesPathsWithoutConfigFile retrieves all directories that do not contain any Component
Parameters:
root: root folder where to start the search
components: list of components already detected
Returns:
list of directories path that does not contain any component
*/
func getDirectoriesWithoutConfigFile(root string, components []Component) []string {
if len(components) == 0 {
return []string{root}
}
directories := []string{}
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if !strings.EqualFold(root, path) && d.IsDir() && !isAnyComponentInPath(path, components) {
directories = getParentFolders(path, directories)
}
return nil
})
if err != nil {
return []string{}
}
return directories
}
/**
* Return all paths which are not sub-folders of some other path within the list
* Target will be added to the list if it is not a sub-folder of any other path within the list
* If a path in the list is sub-folder of Target, that path will be removed.
*
* @param target new path to be added
* @param directories list of all previously added paths
* @return the list containing all paths which are not sub-folders of any other
*/
func getParentFolders(target string, directories []string) []string {
updatedDirectories := []string{}
for _, dir := range directories {
if isFirstPathParentOfSecond(dir, target) {
return directories
}
if isFirstPathParentOfSecond(target, dir) {
continue
}
updatedDirectories = append(updatedDirectories, dir)
}
updatedDirectories = append(updatedDirectories, target)
return updatedDirectories
}
/*
isAnyComponentInPath checks if a component is present in path
Parameters:
path: path where to search for component
components: list of components
Returns:
true if a component is found starting from path
*/
func isAnyComponentInPath(path string, components []Component) bool {
for _, component := range components {
if strings.EqualFold(path, component.Path) || isFirstPathParentOfSecond(component.Path, path) || isFirstPathParentOfSecond(path, component.Path) {
return true
}
}
return false
}
/*
isFirstPathParentOfSecond check if first path is parent (direct or not) of second path
Parameters:
firstPath: path to be used as parent
secondPath: path to be used as child
Returns:
true if firstPath is part of secondPath
*/
func isFirstPathParentOfSecond(firstPath string, secondPath string) bool {
return strings.Contains(secondPath, firstPath)
}
/*
detectComponents detect components by analyzing all files
Parameters:
files: list of files to analyze
Returns:
list of components detected or err if any error occurs
*/
func detectComponents(files []string) ([]Component, error) {
configurationPerLanguage := langfiles.Get().GetConfigurationPerLanguageMapping()
var components []Component
for _, file := range files {
dir, fileName := filepath.Split(file)
if language, isConfig := configurationPerLanguage[fileName]; isConfig && isConfigurationValid(language, file) {
component, err := detectComponent(dir, language)
if err != nil {
return []Component{}, err
}
if component.Path != "" {
components = append(components, component)
}
}
}
return components, nil
}
/*
detectComponent returns a Component if found:
- language must be enabled for component detection
- there should be at least one framework detected
, error otherwise
Parameters:
root: path to be used as root where to start the detection
language: language to be used as target for detection
Returns:
component detected or error if any error occurs
*/
func detectComponent(root string, language string) (Component, error) {
languages, err := Analyze(root)
if err != nil {
return Component{}, err
}
languages = getLanguagesWeightedByConfigFile(languages, language)
if len(languages) > 0 {
if mainLang := languages[0]; mainLang.CanBeComponent && len(mainLang.Frameworks) > 0 {
return Component{
Path: root,
Languages: languages,
}, nil
}
}
return Component{}, nil
}
/*
getLanguagesWeightedByConfigFile returns the list of languages reordered by importance per config file.
Language found by analyzing the config file is used as target.
Parameters:
languages: list of languages to be reordered
languageByConfig: target language
Returns:
list of languages reordered
*/
func getLanguagesWeightedByConfigFile(languages []language.Language, languageByConfig string) []language.Language {
if languageByConfig == "" {
return languages
}
for index, lang := range languages {
if strings.EqualFold(lang.Name, languageByConfig) {
sliceWithoutLang := append(languages[:index], languages[index+1:]...)
return append([]language.Language{lang}, sliceWithoutLang...)
}
}
return languages
}
func isConfigurationValid(language string, file string) bool {
langEnricher := enricher.GetEnricherByLanguage(language)
if langEnricher != nil {
return langEnricher.IsConfigValidForComponentDetection(language, file)
}
return false
}

View File

@@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"errors"
"strings"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
)
type DevFileType struct {
Name string
Language string
ProjectType string
Tags []string
}
func SelectDevFileFromTypes(path string, devFileTypes []DevFileType) (DevFileType, error) {
languages, err := Analyze(path)
if err != nil {
return DevFileType{}, err
}
devfile, err := SelectDevFileUsingLanguagesFromTypes(languages, devFileTypes)
if err != nil {
return DevFileType{}, errors.New("No valid devfile found for project in " + path)
}
return devfile, nil
}
func SelectDevFileUsingLanguagesFromTypes(languages []language.Language, devFileTypes []DevFileType) (DevFileType, error) {
for _, language := range languages {
devfile, err := selectDevFileByLanguage(language, devFileTypes)
if err == nil {
return devfile, nil
}
}
return DevFileType{}, errors.New("no valid devfile found by using those languages")
}
func selectDevFileByLanguage(language language.Language, devFileTypes []DevFileType) (DevFileType, error) {
scoreTarget := 0
devfileTarget := DevFileType{}
FRAMEWORK_WEIGHT := 10
TOOL_WEIGHT := 5
for _, devFile := range devFileTypes {
score := 0
if strings.EqualFold(devFile.Language, language.Name) || matches(language.Aliases, devFile.Language) {
score++
if matches(language.Frameworks, devFile.ProjectType) {
score += FRAMEWORK_WEIGHT
}
for _, tag := range devFile.Tags {
if matches(language.Frameworks, tag) {
score += FRAMEWORK_WEIGHT
}
if matches(language.Tools, tag) {
score += TOOL_WEIGHT
}
}
}
if score > scoreTarget {
scoreTarget = score
devfileTarget = devFile
}
}
if scoreTarget == 0 {
return devfileTarget, errors.New("No valid devfile found for current language " + language.Name)
}
return devfileTarget, nil
}
func matches(values []string, valueToFind string) bool {
for _, value := range values {
if strings.EqualFold(value, valueToFind) {
return true
}
}
return false
}

View File

@@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package recognizer
import (
"os"
"path/filepath"
"sort"
enricher "github.com/redhat-developer/alizer/go/pkg/apis/enricher"
"github.com/redhat-developer/alizer/go/pkg/apis/language"
langfile "github.com/redhat-developer/alizer/go/pkg/utils/langfiles"
)
type languageItem struct {
item langfile.LanguageItem
percentage int
}
func Analyze(path string) ([]language.Language, error) {
languagesFile := langfile.Get()
languagesDetected := make(map[string]languageItem)
paths, err := getFilePaths(path)
if err != nil {
return []language.Language{}, err
}
extensionsGrouped := extractExtensions(paths)
extensionHasProgrammingLanguage := false
totalProgrammingOccurrences := 0
for extension := range extensionsGrouped {
languages := languagesFile.GetLanguagesByExtension(extension)
if len(languages) == 0 {
continue
}
for _, language := range languages {
if language.Kind == "programming" {
var languageFileItem langfile.LanguageItem
var err error
if len(language.Group) == 0 {
languageFileItem = language
} else {
languageFileItem, err = languagesFile.GetLanguageByName(language.Group)
if err != nil {
continue
}
}
tmpLanguageItem := languageItem{languageFileItem, 0}
percentage := languagesDetected[tmpLanguageItem.item.Name].percentage + extensionsGrouped[extension]
tmpLanguageItem.percentage = percentage
languagesDetected[tmpLanguageItem.item.Name] = tmpLanguageItem
extensionHasProgrammingLanguage = true
}
}
if extensionHasProgrammingLanguage {
totalProgrammingOccurrences += extensionsGrouped[extension]
extensionHasProgrammingLanguage = false
}
}
var languagesFound []language.Language
for name, item := range languagesDetected {
tmpPercentage := float64(item.percentage) / float64(totalProgrammingOccurrences)
tmpPercentage = float64(int(tmpPercentage*10000)) / 10000
if tmpPercentage > 0.02 {
tmpLanguage := language.Language{
Name: name,
Aliases: item.item.Aliases,
UsageInPercentage: tmpPercentage * 100,
Frameworks: []string{},
Tools: []string{},
CanBeComponent: item.item.Component}
langEnricher := enricher.GetEnricherByLanguage(name)
if langEnricher != nil {
langEnricher.DoEnrichLanguage(&tmpLanguage, &paths)
}
languagesFound = append(languagesFound, tmpLanguage)
}
}
sort.SliceStable(languagesFound, func(i, j int) bool {
return languagesFound[i].UsageInPercentage > languagesFound[j].UsageInPercentage
})
return languagesFound, nil
}
func extractExtensions(paths []string) map[string]int {
extensions := make(map[string]int)
for _, path := range paths {
extension := filepath.Ext(path)
if len(extension) == 0 {
continue
}
count := extensions[extension] + 1
extensions[extension] = count
}
return extensions
}
func getFilePaths(root string) ([]string, error) {
var files []string
err := filepath.Walk(root,
func(path string, info os.FileInfo, err error) error {
files = append(files, path)
return nil
})
return files, err
}

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2022 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package schema
type DotNetProject struct {
PropertyGroup struct {
TargetFramework string `xml:"TargetFramework"`
TargetFrameworkVersion string `xml:"TargetFrameworkVersion"`
TargetFrameworks string `xml:"TargetFrameworks"`
} `xml:"PropertyGroup"`
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package schema
type LanguageProperties struct {
Type string `yaml:"type,omitempty"`
Color string `yaml:"color,omitempty"`
Extensions []string `yaml:"extensions,omitempty"`
TmScope string `yaml:"tm_scope,omitempty"`
AceMode string `yaml:"ace_mode,omitempty"`
LanguageID int `yaml:"language_id,omitempty"`
Aliases []string `yaml:"aliases,omitempty"`
CodemirrorMode string `yaml:"codemirror_mode,omitempty"`
CodemirrorMimeType string `yaml:"codemirror_mime_type,omitempty"`
Group string `yaml:"group"`
Filenames []string `yaml:"filenames"`
}
type LanguagesProperties map[string]LanguageProperties
type LanguageCustomization struct {
ConfigurationFiles []string `yaml:"configuration_files"`
Component bool `yaml:"component"`
ExcludeFolders []string `yaml:"exclude_folders,omitempty"`
}
type LanguagesCustomizations map[string]LanguageCustomization

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package schema
type PackageJson struct {
Dependencies map[string]string `json:"dependencies"`
}

View File

@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package schema
type Pom struct {
Dependencies struct {
Text string `xml:",chardata"`
Dependency []struct {
Text string `xml:",chardata"`
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
Version string `xml:"version"`
Scope string `xml:"scope"`
} `xml:"dependency"`
} `xml:"dependencies"`
Modules struct {
Module string `xml:"module"`
} `xml:"modules,omitempty"`
}

View File

@@ -0,0 +1,135 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package utils
import (
"encoding/json"
"encoding/xml"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/redhat-developer/alizer/go/pkg/schema"
)
func GetFilesByRegex(filePaths *[]string, regexFile string) []string {
matchedPaths := []string{}
for _, path := range *filePaths {
if isPathOfWantedRegex(path, regexFile) {
matchedPaths = append(matchedPaths, path)
}
}
return matchedPaths
}
func isPathOfWantedRegex(path string, regexFile string) bool {
_, file := filepath.Split(path)
matched, _ := regexp.MatchString(regexFile, file)
return matched
}
func GetFile(filePaths *[]string, wantedFile string) string {
for _, path := range *filePaths {
if IsPathOfWantedFile(path, wantedFile) {
return path
}
}
return ""
}
func HasFile(files *[]string, wantedFile string) bool {
for _, path := range *files {
if IsPathOfWantedFile(path, wantedFile) {
return true
}
}
return false
}
func IsPathOfWantedFile(path string, wantedFile string) bool {
_, file := filepath.Split(path)
return strings.EqualFold(file, wantedFile)
}
func IsTagInFile(file string, tag string) (bool, error) {
contentInByte, err := ioutil.ReadFile(file)
if err != nil {
return false, err
}
content := string(contentInByte)
return strings.Contains(content, tag), nil
}
func IsTagInPomXMLFile(pomFilePath string, tag string) (bool, error) {
pom, err := GetPomFileContent(pomFilePath)
if err != nil {
return false, err
}
for _, dependency := range pom.Dependencies.Dependency {
if strings.Contains(dependency.GroupId, tag) {
return true, nil
}
}
return false, nil
}
func GetPomFileContent(pomFilePath string) (schema.Pom, error) {
xmlFile, err := os.Open(pomFilePath)
if err != nil {
return schema.Pom{}, err
}
byteValue, _ := ioutil.ReadAll(xmlFile)
var pom schema.Pom
xml.Unmarshal(byteValue, &pom)
defer xmlFile.Close()
return pom, nil
}
func IsTagInPackageJsonFile(file string, tag string) bool {
jsonFile, err := os.Open(file)
if err != nil {
return false
}
byteValue, _ := ioutil.ReadAll(jsonFile)
var packageJson schema.PackageJson
json.Unmarshal(byteValue, &packageJson)
defer jsonFile.Close()
if packageJson.Dependencies != nil {
for dependency := range packageJson.Dependencies {
if strings.Contains(dependency, tag) {
return true
}
}
}
return false
}
func AddToArrayIfValueExist(arr *[]string, val string) {
if val != "" {
*arr = append(*arr, val)
}
}
func Contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}

View File

@@ -0,0 +1,152 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc.
******************************************************************************/
package langfiles
import (
"embed"
"errors"
"strings"
"github.com/redhat-developer/alizer/go/pkg/schema"
"gopkg.in/yaml.v3"
)
type LanguageItem struct {
Name string
Aliases []string
Kind string
Group string
ConfigurationFiles []string
ExcludeFolders []string
Component bool
}
type LanguageFile struct {
languages map[string]LanguageItem
extensionsXLanguage map[string][]LanguageItem
}
var (
instance *LanguageFile
//go:embed resources
res embed.FS
)
func Get() *LanguageFile {
if instance == nil {
instance = create()
}
return instance
}
func create() *LanguageFile {
languages := make(map[string]LanguageItem)
extensionsXLanguage := make(map[string][]LanguageItem)
languagesProperties := getLanguagesProperties()
for name, properties := range languagesProperties {
languageItem := LanguageItem{
Name: name,
Aliases: properties.Aliases,
Kind: properties.Type,
Group: properties.Group,
}
customizeLanguage(&languageItem)
languages[name] = languageItem
extensions := properties.Extensions
for _, ext := range extensions {
languagesByExtension := extensionsXLanguage[ext]
languagesByExtension = append(languagesByExtension, languageItem)
extensionsXLanguage[ext] = languagesByExtension
}
}
return &LanguageFile{
languages: languages,
extensionsXLanguage: extensionsXLanguage,
}
}
func customizeLanguage(languageItem *LanguageItem) {
languagesCustomizations := getLanguageCustomizations()
if customization, hasCustomization := languagesCustomizations[(*languageItem).Name]; hasCustomization {
(*languageItem).ConfigurationFiles = customization.ConfigurationFiles
(*languageItem).ExcludeFolders = customization.ExcludeFolders
(*languageItem).Component = customization.Component
}
}
func getLanguagesProperties() schema.LanguagesProperties {
yamlFile, err := res.ReadFile("resources/languages.yml")
if err != nil {
return schema.LanguagesProperties{}
}
var data schema.LanguagesProperties
yaml.Unmarshal(yamlFile, &data)
return data
}
func getLanguageCustomizations() schema.LanguagesCustomizations {
yamlFile, err := res.ReadFile("resources/languages-customization.yml")
if err != nil {
return schema.LanguagesCustomizations{}
}
var data schema.LanguagesCustomizations
yaml.Unmarshal(yamlFile, &data)
return data
}
func (l *LanguageFile) GetLanguagesByExtension(extension string) []LanguageItem {
return l.extensionsXLanguage[extension]
}
func (l *LanguageFile) GetLanguageByName(name string) (LanguageItem, error) {
for langName, langItem := range l.languages {
if langName == name {
return langItem, nil
}
}
return LanguageItem{}, errors.New("no language found with this name")
}
func (l *LanguageFile) GetLanguageByAlias(alias string) (LanguageItem, error) {
for _, langItem := range l.languages {
for _, aliasItem := range langItem.Aliases {
if strings.EqualFold(alias, aliasItem) {
return langItem, nil
}
}
}
return LanguageItem{}, errors.New("no language found with this alias")
}
func (l *LanguageFile) GetLanguageByNameOrAlias(name string) (LanguageItem, error) {
langItem, err := l.GetLanguageByName(name)
if err == nil {
return langItem, nil
}
return l.GetLanguageByAlias(name)
}
func (l *LanguageFile) GetConfigurationPerLanguageMapping() map[string]string {
configurationPerLanguage := make(map[string]string)
for langName, langItem := range l.languages {
configurationFiles := langItem.ConfigurationFiles
for _, configFile := range configurationFiles {
configurationPerLanguage[configFile] = langName
}
}
return configurationPerLanguage
}

View File

@@ -0,0 +1,43 @@
C#:
aliases:
- "dotnet"
configuration_files:
- ".*\\.\\w+proj"
- "appsettings.json"
component: true
F#:
aliases:
- "dotnet"
configuration_files:
- ".*\\.\\w+proj"
- "appsettings.json"
component: true
Go:
configuration_files:
- "go.mod"
- "go.sum"
component: true
Java:
configuration_files:
- "pom.xml"
- "build.gradle"
component: true
JavaScript:
exclude_folders:
- "node_modules"
configuration_files:
- "package.json"
component: true
Python:
component: true
Rust:
configuration_files:
- "Cargo.toml"
component: true
Visual Basic .NET:
aliases:
- "dotnet"
configuration_files:
- ".*\\.\\w+proj"
- "appsettings.json"
component: true

File diff suppressed because it is too large Load Diff

78
vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go generated vendored Normal file
View File

@@ -0,0 +1,78 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package lazyregexp is a thin wrapper over regexp, allowing the use of global
// regexp variables without forcing them to be compiled at init.
package lazyregexp
import (
"os"
"regexp"
"strings"
"sync"
)
// Regexp is a wrapper around regexp.Regexp, where the underlying regexp will be
// compiled the first time it is needed.
type Regexp struct {
str string
once sync.Once
rx *regexp.Regexp
}
func (r *Regexp) re() *regexp.Regexp {
r.once.Do(r.build)
return r.rx
}
func (r *Regexp) build() {
r.rx = regexp.MustCompile(r.str)
r.str = ""
}
func (r *Regexp) FindSubmatch(s []byte) [][]byte {
return r.re().FindSubmatch(s)
}
func (r *Regexp) FindStringSubmatch(s string) []string {
return r.re().FindStringSubmatch(s)
}
func (r *Regexp) FindStringSubmatchIndex(s string) []int {
return r.re().FindStringSubmatchIndex(s)
}
func (r *Regexp) ReplaceAllString(src, repl string) string {
return r.re().ReplaceAllString(src, repl)
}
func (r *Regexp) FindString(s string) string {
return r.re().FindString(s)
}
func (r *Regexp) FindAllString(s string, n int) []string {
return r.re().FindAllString(s, n)
}
func (r *Regexp) MatchString(s string) bool {
return r.re().MatchString(s)
}
func (r *Regexp) SubexpNames() []string {
return r.re().SubexpNames()
}
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
// New creates a new lazy regexp, delaying the compiling work until it is first
// needed. If the code is being run as part of tests, the regexp compiling will
// happen immediately.
func New(str string) *Regexp {
lr := &Regexp{str: str}
if inTest {
// In tests, always compile the regexps early.
lr.re()
}
return lr
}

174
vendor/golang.org/x/mod/modfile/print.go generated vendored Normal file
View File

@@ -0,0 +1,174 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Module file printer.
package modfile
import (
"bytes"
"fmt"
"strings"
)
// Format returns a go.mod file as a byte slice, formatted in standard style.
func Format(f *FileSyntax) []byte {
pr := &printer{}
pr.file(f)
return pr.Bytes()
}
// A printer collects the state during printing of a file or expression.
type printer struct {
bytes.Buffer // output buffer
comment []Comment // pending end-of-line comments
margin int // left margin (indent), a number of tabs
}
// printf prints to the buffer.
func (p *printer) printf(format string, args ...interface{}) {
fmt.Fprintf(p, format, args...)
}
// indent returns the position on the current line, in bytes, 0-indexed.
func (p *printer) indent() int {
b := p.Bytes()
n := 0
for n < len(b) && b[len(b)-1-n] != '\n' {
n++
}
return n
}
// newline ends the current line, flushing end-of-line comments.
func (p *printer) newline() {
if len(p.comment) > 0 {
p.printf(" ")
for i, com := range p.comment {
if i > 0 {
p.trim()
p.printf("\n")
for i := 0; i < p.margin; i++ {
p.printf("\t")
}
}
p.printf("%s", strings.TrimSpace(com.Token))
}
p.comment = p.comment[:0]
}
p.trim()
p.printf("\n")
for i := 0; i < p.margin; i++ {
p.printf("\t")
}
}
// trim removes trailing spaces and tabs from the current line.
func (p *printer) trim() {
// Remove trailing spaces and tabs from line we're about to end.
b := p.Bytes()
n := len(b)
for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') {
n--
}
p.Truncate(n)
}
// file formats the given file into the print buffer.
func (p *printer) file(f *FileSyntax) {
for _, com := range f.Before {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
for i, stmt := range f.Stmt {
switch x := stmt.(type) {
case *CommentBlock:
// comments already handled
p.expr(x)
default:
p.expr(x)
p.newline()
}
for _, com := range stmt.Comment().After {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
if i+1 < len(f.Stmt) {
p.newline()
}
}
}
func (p *printer) expr(x Expr) {
// Emit line-comments preceding this expression.
if before := x.Comment().Before; len(before) > 0 {
// Want to print a line comment.
// Line comments must be at the current margin.
p.trim()
if p.indent() > 0 {
// There's other text on the line. Start a new line.
p.printf("\n")
}
// Re-indent to margin.
for i := 0; i < p.margin; i++ {
p.printf("\t")
}
for _, com := range before {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
}
switch x := x.(type) {
default:
panic(fmt.Errorf("printer: unexpected type %T", x))
case *CommentBlock:
// done
case *LParen:
p.printf("(")
case *RParen:
p.printf(")")
case *Line:
p.tokens(x.Token)
case *LineBlock:
p.tokens(x.Token)
p.printf(" ")
p.expr(&x.LParen)
p.margin++
for _, l := range x.Line {
p.newline()
p.expr(l)
}
p.margin--
p.newline()
p.expr(&x.RParen)
}
// Queue end-of-line comments for printing when we
// reach the end of the line.
p.comment = append(p.comment, x.Comment().Suffix...)
}
func (p *printer) tokens(tokens []string) {
sep := ""
for _, t := range tokens {
if t == "," || t == ")" || t == "]" || t == "}" {
sep = ""
}
p.printf("%s%s", sep, t)
sep = " "
if t == "(" || t == "[" || t == "{" {
sep = ""
}
}
}

959
vendor/golang.org/x/mod/modfile/read.go generated vendored Normal file
View File

@@ -0,0 +1,959 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modfile
import (
"bytes"
"errors"
"fmt"
"os"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
// A Position describes an arbitrary source position in a file, including the
// file, line, column, and byte offset.
type Position struct {
Line int // line in input (starting at 1)
LineRune int // rune in line (starting at 1)
Byte int // byte in input (starting at 0)
}
// add returns the position at the end of s, assuming it starts at p.
func (p Position) add(s string) Position {
p.Byte += len(s)
if n := strings.Count(s, "\n"); n > 0 {
p.Line += n
s = s[strings.LastIndex(s, "\n")+1:]
p.LineRune = 1
}
p.LineRune += utf8.RuneCountInString(s)
return p
}
// An Expr represents an input element.
type Expr interface {
// Span returns the start and end position of the expression,
// excluding leading or trailing comments.
Span() (start, end Position)
// Comment returns the comments attached to the expression.
// This method would normally be named 'Comments' but that
// would interfere with embedding a type of the same name.
Comment() *Comments
}
// A Comment represents a single // comment.
type Comment struct {
Start Position
Token string // without trailing newline
Suffix bool // an end of line (not whole line) comment
}
// Comments collects the comments associated with an expression.
type Comments struct {
Before []Comment // whole-line comments before this expression
Suffix []Comment // end-of-line comments after this expression
// For top-level expressions only, After lists whole-line
// comments following the expression.
After []Comment
}
// Comment returns the receiver. This isn't useful by itself, but
// a Comments struct is embedded into all the expression
// implementation types, and this gives each of those a Comment
// method to satisfy the Expr interface.
func (c *Comments) Comment() *Comments {
return c
}
// A FileSyntax represents an entire go.mod file.
type FileSyntax struct {
Name string // file path
Comments
Stmt []Expr
}
func (x *FileSyntax) Span() (start, end Position) {
if len(x.Stmt) == 0 {
return
}
start, _ = x.Stmt[0].Span()
_, end = x.Stmt[len(x.Stmt)-1].Span()
return start, end
}
// addLine adds a line containing the given tokens to the file.
//
// If the first token of the hint matches the first token of the
// line, the new line is added at the end of the block containing hint,
// extracting hint into a new block if it is not yet in one.
//
// If the hint is non-nil buts its first token does not match,
// the new line is added after the block containing hint
// (or hint itself, if not in a block).
//
// If no hint is provided, addLine appends the line to the end of
// the last block with a matching first token,
// or to the end of the file if no such block exists.
func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
if hint == nil {
// If no hint given, add to the last statement of the given type.
Loop:
for i := len(x.Stmt) - 1; i >= 0; i-- {
stmt := x.Stmt[i]
switch stmt := stmt.(type) {
case *Line:
if stmt.Token != nil && stmt.Token[0] == tokens[0] {
hint = stmt
break Loop
}
case *LineBlock:
if stmt.Token[0] == tokens[0] {
hint = stmt
break Loop
}
}
}
}
newLineAfter := func(i int) *Line {
new := &Line{Token: tokens}
if i == len(x.Stmt) {
x.Stmt = append(x.Stmt, new)
} else {
x.Stmt = append(x.Stmt, nil)
copy(x.Stmt[i+2:], x.Stmt[i+1:])
x.Stmt[i+1] = new
}
return new
}
if hint != nil {
for i, stmt := range x.Stmt {
switch stmt := stmt.(type) {
case *Line:
if stmt == hint {
if stmt.Token == nil || stmt.Token[0] != tokens[0] {
return newLineAfter(i)
}
// Convert line to line block.
stmt.InBlock = true
block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
stmt.Token = stmt.Token[1:]
x.Stmt[i] = block
new := &Line{Token: tokens[1:], InBlock: true}
block.Line = append(block.Line, new)
return new
}
case *LineBlock:
if stmt == hint {
if stmt.Token[0] != tokens[0] {
return newLineAfter(i)
}
new := &Line{Token: tokens[1:], InBlock: true}
stmt.Line = append(stmt.Line, new)
return new
}
for j, line := range stmt.Line {
if line == hint {
if stmt.Token[0] != tokens[0] {
return newLineAfter(i)
}
// Add new line after hint within the block.
stmt.Line = append(stmt.Line, nil)
copy(stmt.Line[j+2:], stmt.Line[j+1:])
new := &Line{Token: tokens[1:], InBlock: true}
stmt.Line[j+1] = new
return new
}
}
}
}
}
new := &Line{Token: tokens}
x.Stmt = append(x.Stmt, new)
return new
}
func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
if line.InBlock {
tokens = tokens[1:]
}
line.Token = tokens
}
// markRemoved modifies line so that it (and its end-of-line comment, if any)
// will be dropped by (*FileSyntax).Cleanup.
func (line *Line) markRemoved() {
line.Token = nil
line.Comments.Suffix = nil
}
// Cleanup cleans up the file syntax x after any edit operations.
// To avoid quadratic behavior, (*Line).markRemoved marks the line as dead
// by setting line.Token = nil but does not remove it from the slice
// in which it appears. After edits have all been indicated,
// calling Cleanup cleans out the dead lines.
func (x *FileSyntax) Cleanup() {
w := 0
for _, stmt := range x.Stmt {
switch stmt := stmt.(type) {
case *Line:
if stmt.Token == nil {
continue
}
case *LineBlock:
ww := 0
for _, line := range stmt.Line {
if line.Token != nil {
stmt.Line[ww] = line
ww++
}
}
if ww == 0 {
continue
}
if ww == 1 {
// Collapse block into single line.
line := &Line{
Comments: Comments{
Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
After: commentsAdd(stmt.Line[0].After, stmt.After),
},
Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
}
x.Stmt[w] = line
w++
continue
}
stmt.Line = stmt.Line[:ww]
}
x.Stmt[w] = stmt
w++
}
x.Stmt = x.Stmt[:w]
}
func commentsAdd(x, y []Comment) []Comment {
return append(x[:len(x):len(x)], y...)
}
func stringsAdd(x, y []string) []string {
return append(x[:len(x):len(x)], y...)
}
// A CommentBlock represents a top-level block of comments separate
// from any rule.
type CommentBlock struct {
Comments
Start Position
}
func (x *CommentBlock) Span() (start, end Position) {
return x.Start, x.Start
}
// A Line is a single line of tokens.
type Line struct {
Comments
Start Position
Token []string
InBlock bool
End Position
}
func (x *Line) Span() (start, end Position) {
return x.Start, x.End
}
// A LineBlock is a factored block of lines, like
//
// require (
// "x"
// "y"
// )
//
type LineBlock struct {
Comments
Start Position
LParen LParen
Token []string
Line []*Line
RParen RParen
}
func (x *LineBlock) Span() (start, end Position) {
return x.Start, x.RParen.Pos.add(")")
}
// An LParen represents the beginning of a parenthesized line block.
// It is a place to store suffix comments.
type LParen struct {
Comments
Pos Position
}
func (x *LParen) Span() (start, end Position) {
return x.Pos, x.Pos.add(")")
}
// An RParen represents the end of a parenthesized line block.
// It is a place to store whole-line (before) comments.
type RParen struct {
Comments
Pos Position
}
func (x *RParen) Span() (start, end Position) {
return x.Pos, x.Pos.add(")")
}
// An input represents a single input file being parsed.
type input struct {
// Lexing state.
filename string // name of input file, for errors
complete []byte // entire input
remaining []byte // remaining input
tokenStart []byte // token being scanned to end of input
token token // next token to be returned by lex, peek
pos Position // current input position
comments []Comment // accumulated comments
// Parser state.
file *FileSyntax // returned top-level syntax tree
parseErrors ErrorList // errors encountered during parsing
// Comment assignment state.
pre []Expr // all expressions, in preorder traversal
post []Expr // all expressions, in postorder traversal
}
func newInput(filename string, data []byte) *input {
return &input{
filename: filename,
complete: data,
remaining: data,
pos: Position{Line: 1, LineRune: 1, Byte: 0},
}
}
// parse parses the input file.
func parse(file string, data []byte) (f *FileSyntax, err error) {
// The parser panics for both routine errors like syntax errors
// and for programmer bugs like array index errors.
// Turn both into error returns. Catching bug panics is
// especially important when processing many files.
in := newInput(file, data)
defer func() {
if e := recover(); e != nil && e != &in.parseErrors {
in.parseErrors = append(in.parseErrors, Error{
Filename: in.filename,
Pos: in.pos,
Err: fmt.Errorf("internal error: %v", e),
})
}
if err == nil && len(in.parseErrors) > 0 {
err = in.parseErrors
}
}()
// Prime the lexer by reading in the first token. It will be available
// in the next peek() or lex() call.
in.readToken()
// Invoke the parser.
in.parseFile()
if len(in.parseErrors) > 0 {
return nil, in.parseErrors
}
in.file.Name = in.filename
// Assign comments to nearby syntax.
in.assignComments()
return in.file, nil
}
// Error is called to report an error.
// Error does not return: it panics.
func (in *input) Error(s string) {
in.parseErrors = append(in.parseErrors, Error{
Filename: in.filename,
Pos: in.pos,
Err: errors.New(s),
})
panic(&in.parseErrors)
}
// eof reports whether the input has reached end of file.
func (in *input) eof() bool {
return len(in.remaining) == 0
}
// peekRune returns the next rune in the input without consuming it.
func (in *input) peekRune() int {
if len(in.remaining) == 0 {
return 0
}
r, _ := utf8.DecodeRune(in.remaining)
return int(r)
}
// peekPrefix reports whether the remaining input begins with the given prefix.
func (in *input) peekPrefix(prefix string) bool {
// This is like bytes.HasPrefix(in.remaining, []byte(prefix))
// but without the allocation of the []byte copy of prefix.
for i := 0; i < len(prefix); i++ {
if i >= len(in.remaining) || in.remaining[i] != prefix[i] {
return false
}
}
return true
}
// readRune consumes and returns the next rune in the input.
func (in *input) readRune() int {
if len(in.remaining) == 0 {
in.Error("internal lexer error: readRune at EOF")
}
r, size := utf8.DecodeRune(in.remaining)
in.remaining = in.remaining[size:]
if r == '\n' {
in.pos.Line++
in.pos.LineRune = 1
} else {
in.pos.LineRune++
}
in.pos.Byte += size
return int(r)
}
type token struct {
kind tokenKind
pos Position
endPos Position
text string
}
type tokenKind int
const (
_EOF tokenKind = -(iota + 1)
_EOLCOMMENT
_IDENT
_STRING
_COMMENT
// newlines and punctuation tokens are allowed as ASCII codes.
)
func (k tokenKind) isComment() bool {
return k == _COMMENT || k == _EOLCOMMENT
}
// isEOL returns whether a token terminates a line.
func (k tokenKind) isEOL() bool {
return k == _EOF || k == _EOLCOMMENT || k == '\n'
}
// startToken marks the beginning of the next input token.
// It must be followed by a call to endToken, once the token's text has
// been consumed using readRune.
func (in *input) startToken() {
in.tokenStart = in.remaining
in.token.text = ""
in.token.pos = in.pos
}
// endToken marks the end of an input token.
// It records the actual token string in tok.text.
// A single trailing newline (LF or CRLF) will be removed from comment tokens.
func (in *input) endToken(kind tokenKind) {
in.token.kind = kind
text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)])
if kind.isComment() {
if strings.HasSuffix(text, "\r\n") {
text = text[:len(text)-2]
} else {
text = strings.TrimSuffix(text, "\n")
}
}
in.token.text = text
in.token.endPos = in.pos
}
// peek returns the kind of the the next token returned by lex.
func (in *input) peek() tokenKind {
return in.token.kind
}
// lex is called from the parser to obtain the next input token.
func (in *input) lex() token {
tok := in.token
in.readToken()
return tok
}
// readToken lexes the next token from the text and stores it in in.token.
func (in *input) readToken() {
// Skip past spaces, stopping at non-space or EOF.
for !in.eof() {
c := in.peekRune()
if c == ' ' || c == '\t' || c == '\r' {
in.readRune()
continue
}
// Comment runs to end of line.
if in.peekPrefix("//") {
in.startToken()
// Is this comment the only thing on its line?
// Find the last \n before this // and see if it's all
// spaces from there to here.
i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
in.readRune()
in.readRune()
// Consume comment.
for len(in.remaining) > 0 && in.readRune() != '\n' {
}
// If we are at top level (not in a statement), hand the comment to
// the parser as a _COMMENT token. The grammar is written
// to handle top-level comments itself.
if !suffix {
in.endToken(_COMMENT)
return
}
// Otherwise, save comment for later attachment to syntax tree.
in.endToken(_EOLCOMMENT)
in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix})
return
}
if in.peekPrefix("/*") {
in.Error("mod files must use // comments (not /* */ comments)")
}
// Found non-space non-comment.
break
}
// Found the beginning of the next token.
in.startToken()
// End of file.
if in.eof() {
in.endToken(_EOF)
return
}
// Punctuation tokens.
switch c := in.peekRune(); c {
case '\n', '(', ')', '[', ']', '{', '}', ',':
in.readRune()
in.endToken(tokenKind(c))
return
case '"', '`': // quoted string
quote := c
in.readRune()
for {
if in.eof() {
in.pos = in.token.pos
in.Error("unexpected EOF in string")
}
if in.peekRune() == '\n' {
in.Error("unexpected newline in string")
}
c := in.readRune()
if c == quote {
break
}
if c == '\\' && quote != '`' {
if in.eof() {
in.pos = in.token.pos
in.Error("unexpected EOF in string")
}
in.readRune()
}
}
in.endToken(_STRING)
return
}
// Checked all punctuation. Must be identifier token.
if c := in.peekRune(); !isIdent(c) {
in.Error(fmt.Sprintf("unexpected input character %#q", c))
}
// Scan over identifier.
for isIdent(in.peekRune()) {
if in.peekPrefix("//") {
break
}
if in.peekPrefix("/*") {
in.Error("mod files must use // comments (not /* */ comments)")
}
in.readRune()
}
in.endToken(_IDENT)
}
// isIdent reports whether c is an identifier rune.
// We treat most printable runes as identifier runes, except for a handful of
// ASCII punctuation characters.
func isIdent(c int) bool {
switch r := rune(c); r {
case ' ', '(', ')', '[', ']', '{', '}', ',':
return false
default:
return !unicode.IsSpace(r) && unicode.IsPrint(r)
}
}
// Comment assignment.
// We build two lists of all subexpressions, preorder and postorder.
// The preorder list is ordered by start location, with outer expressions first.
// The postorder list is ordered by end location, with outer expressions last.
// We use the preorder list to assign each whole-line comment to the syntax
// immediately following it, and we use the postorder list to assign each
// end-of-line comment to the syntax immediately preceding it.
// order walks the expression adding it and its subexpressions to the
// preorder and postorder lists.
func (in *input) order(x Expr) {
if x != nil {
in.pre = append(in.pre, x)
}
switch x := x.(type) {
default:
panic(fmt.Errorf("order: unexpected type %T", x))
case nil:
// nothing
case *LParen, *RParen:
// nothing
case *CommentBlock:
// nothing
case *Line:
// nothing
case *FileSyntax:
for _, stmt := range x.Stmt {
in.order(stmt)
}
case *LineBlock:
in.order(&x.LParen)
for _, l := range x.Line {
in.order(l)
}
in.order(&x.RParen)
}
if x != nil {
in.post = append(in.post, x)
}
}
// assignComments attaches comments to nearby syntax.
func (in *input) assignComments() {
const debug = false
// Generate preorder and postorder lists.
in.order(in.file)
// Split into whole-line comments and suffix comments.
var line, suffix []Comment
for _, com := range in.comments {
if com.Suffix {
suffix = append(suffix, com)
} else {
line = append(line, com)
}
}
if debug {
for _, c := range line {
fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
}
}
// Assign line comments to syntax immediately following.
for _, x := range in.pre {
start, _ := x.Span()
if debug {
fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
}
xcom := x.Comment()
for len(line) > 0 && start.Byte >= line[0].Start.Byte {
if debug {
fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
}
xcom.Before = append(xcom.Before, line[0])
line = line[1:]
}
}
// Remaining line comments go at end of file.
in.file.After = append(in.file.After, line...)
if debug {
for _, c := range suffix {
fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
}
}
// Assign suffix comments to syntax immediately before.
for i := len(in.post) - 1; i >= 0; i-- {
x := in.post[i]
start, end := x.Span()
if debug {
fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
}
// Do not assign suffix comments to end of line block or whole file.
// Instead assign them to the last element inside.
switch x.(type) {
case *FileSyntax:
continue
}
// Do not assign suffix comments to something that starts
// on an earlier line, so that in
//
// x ( y
// z ) // comment
//
// we assign the comment to z and not to x ( ... ).
if start.Line != end.Line {
continue
}
xcom := x.Comment()
for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
if debug {
fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
}
xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
suffix = suffix[:len(suffix)-1]
}
}
// We assigned suffix comments in reverse.
// If multiple suffix comments were appended to the same
// expression node, they are now in reverse. Fix that.
for _, x := range in.post {
reverseComments(x.Comment().Suffix)
}
// Remaining suffix comments go at beginning of file.
in.file.Before = append(in.file.Before, suffix...)
}
// reverseComments reverses the []Comment list.
func reverseComments(list []Comment) {
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
list[i], list[j] = list[j], list[i]
}
}
func (in *input) parseFile() {
in.file = new(FileSyntax)
var cb *CommentBlock
for {
switch in.peek() {
case '\n':
in.lex()
if cb != nil {
in.file.Stmt = append(in.file.Stmt, cb)
cb = nil
}
case _COMMENT:
tok := in.lex()
if cb == nil {
cb = &CommentBlock{Start: tok.pos}
}
com := cb.Comment()
com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text})
case _EOF:
if cb != nil {
in.file.Stmt = append(in.file.Stmt, cb)
}
return
default:
in.parseStmt()
if cb != nil {
in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
cb = nil
}
}
}
}
func (in *input) parseStmt() {
tok := in.lex()
start := tok.pos
end := tok.endPos
tokens := []string{tok.text}
for {
tok := in.lex()
switch {
case tok.kind.isEOL():
in.file.Stmt = append(in.file.Stmt, &Line{
Start: start,
Token: tokens,
End: end,
})
return
case tok.kind == '(':
if next := in.peek(); next.isEOL() {
// Start of block: no more tokens on this line.
in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok))
return
} else if next == ')' {
rparen := in.lex()
if in.peek().isEOL() {
// Empty block.
in.lex()
in.file.Stmt = append(in.file.Stmt, &LineBlock{
Start: start,
Token: tokens,
LParen: LParen{Pos: tok.pos},
RParen: RParen{Pos: rparen.pos},
})
return
}
// '( )' in the middle of the line, not a block.
tokens = append(tokens, tok.text, rparen.text)
} else {
// '(' in the middle of the line, not a block.
tokens = append(tokens, tok.text)
}
default:
tokens = append(tokens, tok.text)
end = tok.endPos
}
}
}
func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock {
x := &LineBlock{
Start: start,
Token: token,
LParen: LParen{Pos: lparen.pos},
}
var comments []Comment
for {
switch in.peek() {
case _EOLCOMMENT:
// Suffix comment, will be attached later by assignComments.
in.lex()
case '\n':
// Blank line. Add an empty comment to preserve it.
in.lex()
if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
comments = append(comments, Comment{})
}
case _COMMENT:
tok := in.lex()
comments = append(comments, Comment{Start: tok.pos, Token: tok.text})
case _EOF:
in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
case ')':
rparen := in.lex()
x.RParen.Before = comments
x.RParen.Pos = rparen.pos
if !in.peek().isEOL() {
in.Error("syntax error (expected newline after closing paren)")
}
in.lex()
return x
default:
l := in.parseLine()
x.Line = append(x.Line, l)
l.Comment().Before = comments
comments = nil
}
}
}
func (in *input) parseLine() *Line {
tok := in.lex()
if tok.kind.isEOL() {
in.Error("internal parse error: parseLine at end of line")
}
start := tok.pos
end := tok.endPos
tokens := []string{tok.text}
for {
tok := in.lex()
if tok.kind.isEOL() {
return &Line{
Start: start,
Token: tokens,
End: end,
InBlock: true,
}
}
tokens = append(tokens, tok.text)
end = tok.endPos
}
}
var (
slashSlash = []byte("//")
moduleStr = []byte("module")
)
// ModulePath returns the module path from the gomod file text.
// If it cannot find a module path, it returns an empty string.
// It is tolerant of unrelated problems in the go.mod file.
func ModulePath(mod []byte) string {
for len(mod) > 0 {
line := mod
mod = nil
if i := bytes.IndexByte(line, '\n'); i >= 0 {
line, mod = line[:i], line[i+1:]
}
if i := bytes.Index(line, slashSlash); i >= 0 {
line = line[:i]
}
line = bytes.TrimSpace(line)
if !bytes.HasPrefix(line, moduleStr) {
continue
}
line = line[len(moduleStr):]
n := len(line)
line = bytes.TrimSpace(line)
if len(line) == n || len(line) == 0 {
continue
}
if line[0] == '"' || line[0] == '`' {
p, err := strconv.Unquote(string(line))
if err != nil {
return "" // malformed quoted string or multiline module path
}
return p
}
return string(line)
}
return "" // missing module path
}

1468
vendor/golang.org/x/mod/modfile/rule.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

844
vendor/golang.org/x/mod/module/module.go generated vendored Normal file
View File

@@ -0,0 +1,844 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package module defines the module.Version type along with support code.
//
// The module.Version type is a simple Path, Version pair:
//
// type Version struct {
// Path string
// Version string
// }
//
// There are no restrictions imposed directly by use of this structure,
// but additional checking functions, most notably Check, verify that
// a particular path, version pair is valid.
//
// Escaped Paths
//
// Module paths appear as substrings of file system paths
// (in the download cache) and of web server URLs in the proxy protocol.
// In general we cannot rely on file systems to be case-sensitive,
// nor can we rely on web servers, since they read from file systems.
// That is, we cannot rely on the file system to keep rsc.io/QUOTE
// and rsc.io/quote separate. Windows and macOS don't.
// Instead, we must never require two different casings of a file path.
// Because we want the download cache to match the proxy protocol,
// and because we want the proxy protocol to be possible to serve
// from a tree of static files (which might be stored on a case-insensitive
// file system), the proxy protocol must never require two different casings
// of a URL path either.
//
// One possibility would be to make the escaped form be the lowercase
// hexadecimal encoding of the actual path bytes. This would avoid ever
// needing different casings of a file path, but it would be fairly illegible
// to most programmers when those paths appeared in the file system
// (including in file paths in compiler errors and stack traces)
// in web server logs, and so on. Instead, we want a safe escaped form that
// leaves most paths unaltered.
//
// The safe escaped form is to replace every uppercase letter
// with an exclamation mark followed by the letter's lowercase equivalent.
//
// For example,
//
// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go.
// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
//
// Import paths that avoid upper-case letters are left unchanged.
// Note that because import paths are ASCII-only and avoid various
// problematic punctuation (like : < and >), the escaped form is also ASCII-only
// and avoids the same problematic punctuation.
//
// Import paths have never allowed exclamation marks, so there is no
// need to define how to escape a literal !.
//
// Unicode Restrictions
//
// Today, paths are disallowed from using Unicode.
//
// Although paths are currently disallowed from using Unicode,
// we would like at some point to allow Unicode letters as well, to assume that
// file systems and URLs are Unicode-safe (storing UTF-8), and apply
// the !-for-uppercase convention for escaping them in the file system.
// But there are at least two subtle considerations.
//
// First, note that not all case-fold equivalent distinct runes
// form an upper/lower pair.
// For example, U+004B ('K'), U+006B ('k'), and U+212A ('' for Kelvin)
// are three distinct runes that case-fold to each other.
// When we do add Unicode letters, we must not assume that upper/lower
// are the only case-equivalent pairs.
// Perhaps the Kelvin symbol would be disallowed entirely, for example.
// Or perhaps it would escape as "!!k", or perhaps as "(212A)".
//
// Second, it would be nice to allow Unicode marks as well as letters,
// but marks include combining marks, and then we must deal not
// only with case folding but also normalization: both U+00E9 ('é')
// and U+0065 U+0301 ('e' followed by combining acute accent)
// look the same on the page and are treated by some file systems
// as the same path. If we do allow Unicode marks in paths, there
// must be some kind of normalization to allow only one canonical
// encoding of any character used in an import path.
package module
// IMPORTANT NOTE
//
// This file essentially defines the set of valid import paths for the go command.
// There are many subtle considerations, including Unicode ambiguity,
// security, network, and file system representations.
//
// This file also defines the set of valid module path and version combinations,
// another topic with many subtle considerations.
//
// Changes to the semantics in this file require approval from rsc.
import (
"fmt"
"path"
"sort"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/mod/semver"
errors "golang.org/x/xerrors"
)
// A Version (for clients, a module.Version) is defined by a module path and version pair.
// These are stored in their plain (unescaped) form.
type Version struct {
// Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
Path string
// Version is usually a semantic version in canonical form.
// There are three exceptions to this general rule.
// First, the top-level target of a build has no specific version
// and uses Version = "".
// Second, during MVS calculations the version "none" is used
// to represent the decision to take no version of a given module.
// Third, filesystem paths found in "replace" directives are
// represented by a path with an empty version.
Version string `json:",omitempty"`
}
// String returns a representation of the Version suitable for logging
// (Path@Version, or just Path if Version is empty).
func (m Version) String() string {
if m.Version == "" {
return m.Path
}
return m.Path + "@" + m.Version
}
// A ModuleError indicates an error specific to a module.
type ModuleError struct {
Path string
Version string
Err error
}
// VersionError returns a ModuleError derived from a Version and error,
// or err itself if it is already such an error.
func VersionError(v Version, err error) error {
var mErr *ModuleError
if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version {
return err
}
return &ModuleError{
Path: v.Path,
Version: v.Version,
Err: err,
}
}
func (e *ModuleError) Error() string {
if v, ok := e.Err.(*InvalidVersionError); ok {
return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err)
}
if e.Version != "" {
return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err)
}
return fmt.Sprintf("module %s: %v", e.Path, e.Err)
}
func (e *ModuleError) Unwrap() error { return e.Err }
// An InvalidVersionError indicates an error specific to a version, with the
// module path unknown or specified externally.
//
// A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError
// must not wrap a ModuleError.
type InvalidVersionError struct {
Version string
Pseudo bool
Err error
}
// noun returns either "version" or "pseudo-version", depending on whether
// e.Version is a pseudo-version.
func (e *InvalidVersionError) noun() string {
if e.Pseudo {
return "pseudo-version"
}
return "version"
}
func (e *InvalidVersionError) Error() string {
return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err)
}
func (e *InvalidVersionError) Unwrap() error { return e.Err }
// An InvalidPathError indicates a module, import, or file path doesn't
// satisfy all naming constraints. See CheckPath, CheckImportPath,
// and CheckFilePath for specific restrictions.
type InvalidPathError struct {
Kind string // "module", "import", or "file"
Path string
Err error
}
func (e *InvalidPathError) Error() string {
return fmt.Sprintf("malformed %s path %q: %v", e.Kind, e.Path, e.Err)
}
func (e *InvalidPathError) Unwrap() error { return e.Err }
// Check checks that a given module path, version pair is valid.
// In addition to the path being a valid module path
// and the version being a valid semantic version,
// the two must correspond.
// For example, the path "yaml/v2" only corresponds to
// semantic versions beginning with "v2.".
func Check(path, version string) error {
if err := CheckPath(path); err != nil {
return err
}
if !semver.IsValid(version) {
return &ModuleError{
Path: path,
Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")},
}
}
_, pathMajor, _ := SplitPathVersion(path)
if err := CheckPathMajor(version, pathMajor); err != nil {
return &ModuleError{Path: path, Err: err}
}
return nil
}
// firstPathOK reports whether r can appear in the first element of a module path.
// The first element of the path must be an LDH domain name, at least for now.
// To avoid case ambiguity, the domain name must be entirely lower case.
func firstPathOK(r rune) bool {
return r == '-' || r == '.' ||
'0' <= r && r <= '9' ||
'a' <= r && r <= 'z'
}
// modPathOK reports whether r can appear in a module path element.
// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
//
// This matches what "go get" has historically recognized in import paths,
// and avoids confusing sequences like '%20' or '+' that would change meaning
// if used in a URL.
//
// TODO(rsc): We would like to allow Unicode letters, but that requires additional
// care in the safe encoding (see "escaped paths" above).
func modPathOK(r rune) bool {
if r < utf8.RuneSelf {
return r == '-' || r == '.' || r == '_' || r == '~' ||
'0' <= r && r <= '9' ||
'A' <= r && r <= 'Z' ||
'a' <= r && r <= 'z'
}
return false
}
// modPathOK reports whether r can appear in a package import path element.
//
// Import paths are intermediate between module paths and file paths: we allow
// disallow characters that would be confusing or ambiguous as arguments to
// 'go get' (such as '@' and ' ' ), but allow certain characters that are
// otherwise-unambiguous on the command line and historically used for some
// binary names (such as '++' as a suffix for compiler binaries and wrappers).
func importPathOK(r rune) bool {
return modPathOK(r) || r == '+'
}
// fileNameOK reports whether r can appear in a file name.
// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
// If we expand the set of allowed characters here, we have to
// work harder at detecting potential case-folding and normalization collisions.
// See note about "escaped paths" above.
func fileNameOK(r rune) bool {
if r < utf8.RuneSelf {
// Entire set of ASCII punctuation, from which we remove characters:
// ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
// We disallow some shell special characters: " ' * < > ? ` |
// (Note that some of those are disallowed by the Windows file system as well.)
// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
// We allow spaces (U+0020) in file names.
const allowed = "!#$%&()+,-.=@[]^_{}~ "
if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
return true
}
for i := 0; i < len(allowed); i++ {
if rune(allowed[i]) == r {
return true
}
}
return false
}
// It may be OK to add more ASCII punctuation here, but only carefully.
// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
return unicode.IsLetter(r)
}
// CheckPath checks that a module path is valid.
// A valid module path is a valid import path, as checked by CheckImportPath,
// with three additional constraints.
// First, the leading path element (up to the first slash, if any),
// by convention a domain name, must contain only lower-case ASCII letters,
// ASCII digits, dots (U+002E), and dashes (U+002D);
// it must contain at least one dot and cannot start with a dash.
// Second, for a final path element of the form /vN, where N looks numeric
// (ASCII digits and dots) must not begin with a leading zero, must not be /v1,
// and must not contain any dots. For paths beginning with "gopkg.in/",
// this second requirement is replaced by a requirement that the path
// follow the gopkg.in server's conventions.
// Third, no path element may begin with a dot.
func CheckPath(path string) (err error) {
defer func() {
if err != nil {
err = &InvalidPathError{Kind: "module", Path: path, Err: err}
}
}()
if err := checkPath(path, modulePath); err != nil {
return err
}
i := strings.Index(path, "/")
if i < 0 {
i = len(path)
}
if i == 0 {
return fmt.Errorf("leading slash")
}
if !strings.Contains(path[:i], ".") {
return fmt.Errorf("missing dot in first path element")
}
if path[0] == '-' {
return fmt.Errorf("leading dash in first path element")
}
for _, r := range path[:i] {
if !firstPathOK(r) {
return fmt.Errorf("invalid char %q in first path element", r)
}
}
if _, _, ok := SplitPathVersion(path); !ok {
return fmt.Errorf("invalid version")
}
return nil
}
// CheckImportPath checks that an import path is valid.
//
// A valid import path consists of one or more valid path elements
// separated by slashes (U+002F). (It must not begin with nor end in a slash.)
//
// A valid path element is a non-empty string made up of
// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
// It must not end with a dot (U+002E), nor contain two dots in a row.
//
// The element prefix up to the first dot must not be a reserved file name
// on Windows, regardless of case (CON, com1, NuL, and so on). The element
// must not have a suffix of a tilde followed by one or more ASCII digits
// (to exclude paths elements that look like Windows short-names).
//
// CheckImportPath may be less restrictive in the future, but see the
// top-level package documentation for additional information about
// subtleties of Unicode.
func CheckImportPath(path string) error {
if err := checkPath(path, importPath); err != nil {
return &InvalidPathError{Kind: "import", Path: path, Err: err}
}
return nil
}
// pathKind indicates what kind of path we're checking. Module paths,
// import paths, and file paths have different restrictions.
type pathKind int
const (
modulePath pathKind = iota
importPath
filePath
)
// checkPath checks that a general path is valid. kind indicates what
// specific constraints should be applied.
//
// checkPath returns an error describing why the path is not valid.
// Because these checks apply to module, import, and file paths,
// and because other checks may be applied, the caller is expected to wrap
// this error with InvalidPathError.
func checkPath(path string, kind pathKind) error {
if !utf8.ValidString(path) {
return fmt.Errorf("invalid UTF-8")
}
if path == "" {
return fmt.Errorf("empty string")
}
if path[0] == '-' && kind != filePath {
return fmt.Errorf("leading dash")
}
if strings.Contains(path, "//") {
return fmt.Errorf("double slash")
}
if path[len(path)-1] == '/' {
return fmt.Errorf("trailing slash")
}
elemStart := 0
for i, r := range path {
if r == '/' {
if err := checkElem(path[elemStart:i], kind); err != nil {
return err
}
elemStart = i + 1
}
}
if err := checkElem(path[elemStart:], kind); err != nil {
return err
}
return nil
}
// checkElem checks whether an individual path element is valid.
func checkElem(elem string, kind pathKind) error {
if elem == "" {
return fmt.Errorf("empty path element")
}
if strings.Count(elem, ".") == len(elem) {
return fmt.Errorf("invalid path element %q", elem)
}
if elem[0] == '.' && kind == modulePath {
return fmt.Errorf("leading dot in path element")
}
if elem[len(elem)-1] == '.' {
return fmt.Errorf("trailing dot in path element")
}
for _, r := range elem {
ok := false
switch kind {
case modulePath:
ok = modPathOK(r)
case importPath:
ok = importPathOK(r)
case filePath:
ok = fileNameOK(r)
default:
panic(fmt.Sprintf("internal error: invalid kind %v", kind))
}
if !ok {
return fmt.Errorf("invalid char %q", r)
}
}
// Windows disallows a bunch of path elements, sadly.
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
short := elem
if i := strings.Index(short, "."); i >= 0 {
short = short[:i]
}
for _, bad := range badWindowsNames {
if strings.EqualFold(bad, short) {
return fmt.Errorf("%q disallowed as path element component on Windows", short)
}
}
if kind == filePath {
// don't check for Windows short-names in file names. They're
// only an issue for import paths.
return nil
}
// Reject path components that look like Windows short-names.
// Those usually end in a tilde followed by one or more ASCII digits.
if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 {
suffix := short[tilde+1:]
suffixIsDigits := true
for _, r := range suffix {
if r < '0' || r > '9' {
suffixIsDigits = false
break
}
}
if suffixIsDigits {
return fmt.Errorf("trailing tilde and digits in path element")
}
}
return nil
}
// CheckFilePath checks that a slash-separated file path is valid.
// The definition of a valid file path is the same as the definition
// of a valid import path except that the set of allowed characters is larger:
// all Unicode letters, ASCII digits, the ASCII space character (U+0020),
// and the ASCII punctuation characters
// “!#$%&()+,-.=@[]^_{}~”.
// (The excluded punctuation characters, " * < > ? ` ' | / \ and :,
// have special meanings in certain shells or operating systems.)
//
// CheckFilePath may be less restrictive in the future, but see the
// top-level package documentation for additional information about
// subtleties of Unicode.
func CheckFilePath(path string) error {
if err := checkPath(path, filePath); err != nil {
return &InvalidPathError{Kind: "file", Path: path, Err: err}
}
return nil
}
// badWindowsNames are the reserved file path elements on Windows.
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
var badWindowsNames = []string{
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
}
// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
// and version is either empty or "/vN" for N >= 2.
// As a special case, gopkg.in paths are recognized directly;
// they require ".vN" instead of "/vN", and for all N, not just N >= 2.
// SplitPathVersion returns with ok = false when presented with
// a path whose last path element does not satisfy the constraints
// applied by CheckPath, such as "example.com/pkg/v1" or "example.com/pkg/v1.2".
func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
if strings.HasPrefix(path, "gopkg.in/") {
return splitGopkgIn(path)
}
i := len(path)
dot := false
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
if path[i-1] == '.' {
dot = true
}
i--
}
if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
return path, "", true
}
prefix, pathMajor = path[:i-2], path[i-2:]
if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
return path, "", false
}
return prefix, pathMajor, true
}
// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
if !strings.HasPrefix(path, "gopkg.in/") {
return path, "", false
}
i := len(path)
if strings.HasSuffix(path, "-unstable") {
i -= len("-unstable")
}
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
i--
}
if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
// All gopkg.in paths must end in vN for some N.
return path, "", false
}
prefix, pathMajor = path[:i-2], path[i-2:]
if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
return path, "", false
}
return prefix, pathMajor, true
}
// MatchPathMajor reports whether the semantic version v
// matches the path major version pathMajor.
//
// MatchPathMajor returns true if and only if CheckPathMajor returns nil.
func MatchPathMajor(v, pathMajor string) bool {
return CheckPathMajor(v, pathMajor) == nil
}
// CheckPathMajor returns a non-nil error if the semantic version v
// does not match the path major version pathMajor.
func CheckPathMajor(v, pathMajor string) error {
// TODO(jayconrod): return errors or panic for invalid inputs. This function
// (and others) was covered by integration tests for cmd/go, and surrounding
// code protected against invalid inputs like non-canonical versions.
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
}
if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
return nil
}
m := semver.Major(v)
if pathMajor == "" {
if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" {
return nil
}
pathMajor = "v0 or v1"
} else if pathMajor[0] == '/' || pathMajor[0] == '.' {
if m == pathMajor[1:] {
return nil
}
pathMajor = pathMajor[1:]
}
return &InvalidVersionError{
Version: v,
Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)),
}
}
// PathMajorPrefix returns the major-version tag prefix implied by pathMajor.
// An empty PathMajorPrefix allows either v0 or v1.
//
// Note that MatchPathMajor may accept some versions that do not actually begin
// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1'
// pathMajor, even though that pathMajor implies 'v1' tagging.
func PathMajorPrefix(pathMajor string) string {
if pathMajor == "" {
return ""
}
if pathMajor[0] != '/' && pathMajor[0] != '.' {
panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator")
}
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
}
m := pathMajor[1:]
if m != semver.Major(m) {
panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version")
}
return m
}
// CanonicalVersion returns the canonical form of the version string v.
// It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible".
func CanonicalVersion(v string) string {
cv := semver.Canonical(v)
if semver.Build(v) == "+incompatible" {
cv += "+incompatible"
}
return cv
}
// Sort sorts the list by Path, breaking ties by comparing Version fields.
// The Version fields are interpreted as semantic versions (using semver.Compare)
// optionally followed by a tie-breaking suffix introduced by a slash character,
// like in "v0.0.1/go.mod".
func Sort(list []Version) {
sort.Slice(list, func(i, j int) bool {
mi := list[i]
mj := list[j]
if mi.Path != mj.Path {
return mi.Path < mj.Path
}
// To help go.sum formatting, allow version/file.
// Compare semver prefix by semver rules,
// file by string order.
vi := mi.Version
vj := mj.Version
var fi, fj string
if k := strings.Index(vi, "/"); k >= 0 {
vi, fi = vi[:k], vi[k:]
}
if k := strings.Index(vj, "/"); k >= 0 {
vj, fj = vj[:k], vj[k:]
}
if vi != vj {
return semver.Compare(vi, vj) < 0
}
return fi < fj
})
}
// EscapePath returns the escaped form of the given module path.
// It fails if the module path is invalid.
func EscapePath(path string) (escaped string, err error) {
if err := CheckPath(path); err != nil {
return "", err
}
return escapeString(path)
}
// EscapeVersion returns the escaped form of the given module version.
// Versions are allowed to be in non-semver form but must be valid file names
// and not contain exclamation marks.
func EscapeVersion(v string) (escaped string, err error) {
if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") {
return "", &InvalidVersionError{
Version: v,
Err: fmt.Errorf("disallowed version string"),
}
}
return escapeString(v)
}
func escapeString(s string) (escaped string, err error) {
haveUpper := false
for _, r := range s {
if r == '!' || r >= utf8.RuneSelf {
// This should be disallowed by CheckPath, but diagnose anyway.
// The correctness of the escaping loop below depends on it.
return "", fmt.Errorf("internal error: inconsistency in EscapePath")
}
if 'A' <= r && r <= 'Z' {
haveUpper = true
}
}
if !haveUpper {
return s, nil
}
var buf []byte
for _, r := range s {
if 'A' <= r && r <= 'Z' {
buf = append(buf, '!', byte(r+'a'-'A'))
} else {
buf = append(buf, byte(r))
}
}
return string(buf), nil
}
// UnescapePath returns the module path for the given escaped path.
// It fails if the escaped path is invalid or describes an invalid path.
func UnescapePath(escaped string) (path string, err error) {
path, ok := unescapeString(escaped)
if !ok {
return "", fmt.Errorf("invalid escaped module path %q", escaped)
}
if err := CheckPath(path); err != nil {
return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err)
}
return path, nil
}
// UnescapeVersion returns the version string for the given escaped version.
// It fails if the escaped form is invalid or describes an invalid version.
// Versions are allowed to be in non-semver form but must be valid file names
// and not contain exclamation marks.
func UnescapeVersion(escaped string) (v string, err error) {
v, ok := unescapeString(escaped)
if !ok {
return "", fmt.Errorf("invalid escaped version %q", escaped)
}
if err := checkElem(v, filePath); err != nil {
return "", fmt.Errorf("invalid escaped version %q: %v", v, err)
}
return v, nil
}
func unescapeString(escaped string) (string, bool) {
var buf []byte
bang := false
for _, r := range escaped {
if r >= utf8.RuneSelf {
return "", false
}
if bang {
bang = false
if r < 'a' || 'z' < r {
return "", false
}
buf = append(buf, byte(r+'A'-'a'))
continue
}
if r == '!' {
bang = true
continue
}
if 'A' <= r && r <= 'Z' {
return "", false
}
buf = append(buf, byte(r))
}
if bang {
return "", false
}
return string(buf), true
}
// MatchPrefixPatterns reports whether any path prefix of target matches one of
// the glob patterns (as defined by path.Match) in the comma-separated globs
// list. This implements the algorithm used when matching a module path to the
// GOPRIVATE environment variable, as described by 'go help module-private'.
//
// It ignores any empty or malformed patterns in the list.
func MatchPrefixPatterns(globs, target string) bool {
for globs != "" {
// Extract next non-empty glob in comma-separated list.
var glob string
if i := strings.Index(globs, ","); i >= 0 {
glob, globs = globs[:i], globs[i+1:]
} else {
glob, globs = globs, ""
}
if glob == "" {
continue
}
// A glob with N+1 path elements (N slashes) needs to be matched
// against the first N+1 path elements of target,
// which end just before the N+1'th slash.
n := strings.Count(glob, "/")
prefix := target
// Walk target, counting slashes, truncating at the N+1'th slash.
for i := 0; i < len(target); i++ {
if target[i] == '/' {
if n == 0 {
prefix = target[:i]
break
}
n--
}
}
if n > 0 {
// Not enough prefix elements.
continue
}
matched, _ := path.Match(glob, prefix)
if matched {
return true
}
}
return false
}

250
vendor/golang.org/x/mod/module/pseudo.go generated vendored Normal file
View File

@@ -0,0 +1,250 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Pseudo-versions
//
// Code authors are expected to tag the revisions they want users to use,
// including prereleases. However, not all authors tag versions at all,
// and not all commits a user might want to try will have tags.
// A pseudo-version is a version with a special form that allows us to
// address an untagged commit and order that version with respect to
// other versions we might encounter.
//
// A pseudo-version takes one of the general forms:
//
// (1) vX.0.0-yyyymmddhhmmss-abcdef123456
// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible
//
// If there is no recently tagged version with the right major version vX,
// then form (1) is used, creating a space of pseudo-versions at the bottom
// of the vX version range, less than any tagged version, including the unlikely v0.0.0.
//
// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,
// then the pseudo-version uses form (2) or (3), making it a prerelease for the next
// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string
// ensures that the pseudo-version compares less than possible future explicit prereleases
// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.
//
// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,
// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.
package module
import (
"errors"
"fmt"
"strings"
"time"
"golang.org/x/mod/internal/lazyregexp"
"golang.org/x/mod/semver"
)
var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`)
const PseudoVersionTimestampFormat = "20060102150405"
// PseudoVersion returns a pseudo-version for the given major version ("v1")
// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,
// and revision identifier (usually a 12-byte commit hash prefix).
func PseudoVersion(major, older string, t time.Time, rev string) string {
if major == "" {
major = "v0"
}
segment := fmt.Sprintf("%s-%s", t.UTC().Format(PseudoVersionTimestampFormat), rev)
build := semver.Build(older)
older = semver.Canonical(older)
if older == "" {
return major + ".0.0-" + segment // form (1)
}
if semver.Prerelease(older) != "" {
return older + ".0." + segment + build // form (4), (5)
}
// Form (2), (3).
// Extract patch from vMAJOR.MINOR.PATCH
i := strings.LastIndex(older, ".") + 1
v, patch := older[:i], older[i:]
// Reassemble.
return v + incDecimal(patch) + "-0." + segment + build
}
// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and
// revision, which may be used as a placeholder.
func ZeroPseudoVersion(major string) string {
return PseudoVersion(major, "", time.Time{}, "000000000000")
}
// incDecimal returns the decimal string incremented by 1.
func incDecimal(decimal string) string {
// Scan right to left turning 9s to 0s until you find a digit to increment.
digits := []byte(decimal)
i := len(digits) - 1
for ; i >= 0 && digits[i] == '9'; i-- {
digits[i] = '0'
}
if i >= 0 {
digits[i]++
} else {
// digits is all zeros
digits[0] = '1'
digits = append(digits, '0')
}
return string(digits)
}
// decDecimal returns the decimal string decremented by 1, or the empty string
// if the decimal is all zeroes.
func decDecimal(decimal string) string {
// Scan right to left turning 0s to 9s until you find a digit to decrement.
digits := []byte(decimal)
i := len(digits) - 1
for ; i >= 0 && digits[i] == '0'; i-- {
digits[i] = '9'
}
if i < 0 {
// decimal is all zeros
return ""
}
if i == 0 && digits[i] == '1' && len(digits) > 1 {
digits = digits[1:]
} else {
digits[i]--
}
return string(digits)
}
// IsPseudoVersion reports whether v is a pseudo-version.
func IsPseudoVersion(v string) bool {
return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v)
}
// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base,
// timestamp, and revision, as returned by ZeroPseudoVersion.
func IsZeroPseudoVersion(v string) bool {
return v == ZeroPseudoVersion(semver.Major(v))
}
// PseudoVersionTime returns the time stamp of the pseudo-version v.
// It returns an error if v is not a pseudo-version or if the time stamp
// embedded in the pseudo-version is not a valid time.
func PseudoVersionTime(v string) (time.Time, error) {
_, timestamp, _, _, err := parsePseudoVersion(v)
if err != nil {
return time.Time{}, err
}
t, err := time.Parse("20060102150405", timestamp)
if err != nil {
return time.Time{}, &InvalidVersionError{
Version: v,
Pseudo: true,
Err: fmt.Errorf("malformed time %q", timestamp),
}
}
return t, nil
}
// PseudoVersionRev returns the revision identifier of the pseudo-version v.
// It returns an error if v is not a pseudo-version.
func PseudoVersionRev(v string) (rev string, err error) {
_, _, rev, _, err = parsePseudoVersion(v)
return
}
// PseudoVersionBase returns the canonical parent version, if any, upon which
// the pseudo-version v is based.
//
// If v has no parent version (that is, if it is "vX.0.0-[…]"),
// PseudoVersionBase returns the empty string and a nil error.
func PseudoVersionBase(v string) (string, error) {
base, _, _, build, err := parsePseudoVersion(v)
if err != nil {
return "", err
}
switch pre := semver.Prerelease(base); pre {
case "":
// vX.0.0-yyyymmddhhmmss-abcdef123456 → ""
if build != "" {
// Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible
// are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag,
// but the "+incompatible" suffix implies that the major version of
// the parent tag is not compatible with the module's import path.
//
// There are a few such entries in the index generated by proxy.golang.org,
// but we believe those entries were generated by the proxy itself.
return "", &InvalidVersionError{
Version: v,
Pseudo: true,
Err: fmt.Errorf("lacks base version, but has build metadata %q", build),
}
}
return "", nil
case "-0":
// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z
// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible
base = strings.TrimSuffix(base, pre)
i := strings.LastIndexByte(base, '.')
if i < 0 {
panic("base from parsePseudoVersion missing patch number: " + base)
}
patch := decDecimal(base[i+1:])
if patch == "" {
// vX.0.0-0 is invalid, but has been observed in the wild in the index
// generated by requests to proxy.golang.org.
//
// NOTE(bcmills): I cannot find a historical bug that accounts for
// pseudo-versions of this form, nor have I seen such versions in any
// actual go.mod files. If we find actual examples of this form and a
// reasonable theory of how they came into existence, it seems fine to
// treat them as equivalent to vX.0.0 (especially since the invalid
// pseudo-versions have lower precedence than the real ones). For now, we
// reject them.
return "", &InvalidVersionError{
Version: v,
Pseudo: true,
Err: fmt.Errorf("version before %s would have negative patch number", base),
}
}
return base[:i+1] + patch + build, nil
default:
// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre
// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible
if !strings.HasSuffix(base, ".0") {
panic(`base from parsePseudoVersion missing ".0" before date: ` + base)
}
return strings.TrimSuffix(base, ".0") + build, nil
}
}
var errPseudoSyntax = errors.New("syntax error")
func parsePseudoVersion(v string) (base, timestamp, rev, build string, err error) {
if !IsPseudoVersion(v) {
return "", "", "", "", &InvalidVersionError{
Version: v,
Pseudo: true,
Err: errPseudoSyntax,
}
}
build = semver.Build(v)
v = strings.TrimSuffix(v, build)
j := strings.LastIndex(v, "-")
v, rev = v[:j], v[j+1:]
i := strings.LastIndex(v, "-")
if j := strings.LastIndex(v, "."); j > i {
base = v[:j] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0"
timestamp = v[j+1:]
} else {
base = v[:i] // "vX.0.0"
timestamp = v[i+1:]
}
return base, timestamp, rev, build, nil
}

View File

@@ -22,6 +22,8 @@
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
package semver
import "sort"
// parsed returns the parsed form of a semantic version string.
type parsed struct {
major string
@@ -150,6 +152,24 @@ func Max(v, w string) string {
return w
}
// ByVersion implements sort.Interface for sorting semantic version strings.
type ByVersion []string
func (vs ByVersion) Len() int { return len(vs) }
func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
func (vs ByVersion) Less(i, j int) bool {
cmp := Compare(vs[i], vs[j])
if cmp != 0 {
return cmp < 0
}
return vs[i] < vs[j]
}
// Sort sorts a list of semantic version strings using ByVersion.
func Sort(list []string) {
sort.Sort(ByVersion(list))
}
func parse(v string) (p parsed, ok bool) {
if v == "" || v[0] != 'v' {
p.err = "missing v prefix"

18
vendor/modules.txt vendored
View File

@@ -515,6 +515,19 @@ github.com/prometheus/common/model
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
# github.com/redhat-developer/alizer/go v0.0.0-20220215154256-33df7feef4ae
## explicit
github.com/redhat-developer/alizer/go/pkg/apis/enricher
github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/dotnet
github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/go
github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/java
github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/javascript/nodejs
github.com/redhat-developer/alizer/go/pkg/apis/enricher/framework/python
github.com/redhat-developer/alizer/go/pkg/apis/language
github.com/redhat-developer/alizer/go/pkg/apis/recognizer
github.com/redhat-developer/alizer/go/pkg/schema
github.com/redhat-developer/alizer/go/pkg/utils
github.com/redhat-developer/alizer/go/pkg/utils/langfiles
# github.com/redhat-developer/service-binding-operator v0.9.0
## explicit
github.com/redhat-developer/service-binding-operator/apis
@@ -633,7 +646,10 @@ golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
golang.org/x/crypto/ssh/knownhosts
# golang.org/x/mod v0.4.2
# golang.org/x/mod v0.5.1
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/module
golang.org/x/mod/semver
# golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/net/context