Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
159 lines
4.2 KiB
Go
159 lines
4.2 KiB
Go
// Package gomodulepath implements functions for the manipulation of Go module paths.
|
|
// Paths are typically defined as a domain name and a path containing the user and
|
|
// repository names, e.g. "github.com/username/reponame", but Go also allows other module
|
|
// names like "domain.com/name", "name", "namespace/name", or similar variants.
|
|
package gomodulepath
|
|
|
|
import (
|
|
"fmt"
|
|
"go/parser"
|
|
"go/token"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"golang.org/x/mod/module"
|
|
"golang.org/x/mod/semver"
|
|
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
|
|
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/gomodule"
|
|
)
|
|
|
|
// Path represents a Go module's path.
|
|
type Path struct {
|
|
// Path is Go module's full path.
|
|
// e.g.: github.com/ignite/cli.
|
|
RawPath string
|
|
|
|
// Root is the root directory name of Go module.
|
|
// e.g.: cli for github.com/ignite/cli.
|
|
Root string
|
|
|
|
// Package is the default package name for the Go module that can be used
|
|
// to host main functionality of the module.
|
|
// e.g.: cli for github.com/ignite/cli.
|
|
Package string
|
|
}
|
|
|
|
// Parse parses rawpath into a module Path.
|
|
func Parse(rawpath string) (Path, error) {
|
|
if err := validateRawPath(rawpath); err != nil {
|
|
return Path{}, err
|
|
}
|
|
rootName := root(rawpath)
|
|
// package name cannot contain "-" so gracefully remove them
|
|
// if they present.
|
|
packageName := stripNonAlphaNumeric(rootName)
|
|
if err := validatePackageName(packageName); err != nil {
|
|
return Path{}, err
|
|
}
|
|
p := Path{
|
|
RawPath: rawpath,
|
|
Root: rootName,
|
|
Package: packageName,
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// ParseAt parses Go module path of an app resides at path.
|
|
func ParseAt(path string) (Path, error) {
|
|
parsed, err := gomodule.ParseAt(path)
|
|
if err != nil {
|
|
return Path{}, err
|
|
}
|
|
return Parse(parsed.Module.Mod.Path)
|
|
}
|
|
|
|
// Find search the Go module in the current and parent paths until finding it.
|
|
func Find(path string) (parsed Path, appPath string, err error) {
|
|
for len(path) != 0 && path != "." && path != "/" {
|
|
parsed, err = ParseAt(path)
|
|
if errors.Is(err, gomodule.ErrGoModNotFound) {
|
|
path = filepath.Dir(path)
|
|
continue
|
|
}
|
|
return parsed, path, err
|
|
}
|
|
return Path{}, "", errors.Wrap(gomodule.ErrGoModNotFound, "could not locate your app's root dir")
|
|
}
|
|
|
|
// ExtractAppPath extracts the app module path from a Go module path.
|
|
func ExtractAppPath(path string) string {
|
|
if path == "" {
|
|
return ""
|
|
}
|
|
|
|
items := strings.Split(path, "/")
|
|
|
|
// Remove the first path item if it is assumed to be a domain name
|
|
if len(items) > 1 && strings.Contains(items[0], ".") {
|
|
items = items[1:]
|
|
}
|
|
|
|
count := len(items)
|
|
if count == 1 {
|
|
// The Go module path is a single name
|
|
return items[0]
|
|
}
|
|
|
|
// The last two items in the path define the namespace and app name
|
|
return strings.Join(items[count-2:], "/")
|
|
}
|
|
|
|
func hasDomainNamePrefix(path string) bool {
|
|
if path == "" {
|
|
return false
|
|
}
|
|
|
|
// TODO: should we use a regexp instead of the simplistic check ?
|
|
name, _, _ := strings.Cut(path, "/")
|
|
return strings.Contains(name, ".")
|
|
}
|
|
|
|
func validateRawPath(path string) error {
|
|
// A raw path should be either a URI, a single name or a path
|
|
if hasDomainNamePrefix(path) {
|
|
return validateURIPath(path)
|
|
}
|
|
return validateNamePath(path)
|
|
}
|
|
|
|
func validateURIPath(path string) error {
|
|
if err := module.CheckPath(path); err != nil {
|
|
return errors.Errorf("app name is an invalid go module name: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateNamePath(path string) error {
|
|
if err := module.CheckImportPath(path); err != nil {
|
|
return errors.Errorf("app name is an invalid go module name: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validatePackageName(name string) error {
|
|
fset := token.NewFileSet()
|
|
src := fmt.Sprintf("package %s", name)
|
|
if _, err := parser.ParseFile(fset, "", src, parser.PackageClauseOnly); err != nil {
|
|
// parser error is very low level here so let's hide it from the user
|
|
// completely.
|
|
return errors.New("app name is an invalid go package name")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func root(path string) string {
|
|
sp := strings.Split(path, "/")
|
|
name := sp[len(sp)-1]
|
|
if semver.IsValid(name) { // omit versions.
|
|
name = sp[len(sp)-2]
|
|
}
|
|
return name
|
|
}
|
|
|
|
func stripNonAlphaNumeric(name string) string {
|
|
reg := regexp.MustCompile(`[^a-zA-Z0-9_]+`)
|
|
return strings.ToLower(reg.ReplaceAllString(name, ""))
|
|
}
|