From e270c019f5d692e7f54d1fa8c185165c0796a857 Mon Sep 17 00:00:00 2001 From: RainrainWu Date: Thu, 1 Dec 2022 20:27:07 +0800 Subject: [PATCH] Highlight Specific Dependencies (#23) * feat($atlas): add highlight flag Signed-off-by: Rain Wu --- README.md | 4 ++- main.go | 59 +++++++++++++++++++++++++++++++++++++------ telescope/atlas.go | 62 ++++++++++++++++++++++++++++++++-------------- telescope/scope.go | 28 +++++++++++++++++---- 4 files changed, 120 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 0a3c204..05842d6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ Telescope is a dependencies scanner that helps developers sort out outdated depe ``` $ docker run --rm docker.io/r41nwu/telescope:latest -Usage: telescope [-f file_path] [-s outdated_scope] [-i ignored_dependency] [--skip-unknown] [--strict-semver] +Usage: telescope [-f file_path] [-s outdated_scope] [-i ignored_dependency] [-c critical_dependency] [--skip-unknown] [--strict-semver] + -c value + highlight critical dependency -f string dependencies file path (default "go.mod") -i value diff --git a/main.go b/main.go index b0277b7..acb45df 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "strings" "telescope/telescope" ) @@ -24,12 +25,50 @@ func (i *IgnoredDependencies) Set(value string) error { return nil } +type CriticalDependencies map[string]telescope.OutdatedScope + +func (c *CriticalDependencies) String() string { + + var buffer string + for dep, scp := range map[string]telescope.OutdatedScope(*c) { + + buffer += fmt.Sprintf("[ %s ] %s\n", scp, dep) + } + return buffer +} + +func (c *CriticalDependencies) Set(value string) error { + + var depName string + var desiredScope telescope.OutdatedScope + + result := strings.Split(value, ":") + c_deps := map[string]telescope.OutdatedScope(*c) + if len(result) == 2 { + depName, desiredScope = result[1], telescope.OutdatedScopeStrToEnum(result[0]) + } else if len(result) == 1 { + depName, desiredScope = result[0], telescope.MAJOR + } else { + panic(fmt.Errorf("invalid expression: %s", value)) + } + + if registeredScope, ok := c_deps[depName]; ok { + c_deps[depName] = telescope.GetTopScope( + []telescope.OutdatedScope{registeredScope, desiredScope}, + ) + } else { + c_deps[depName] = desiredScope + } + return nil +} + var ( - filePath string - outdatedScope string - skipUnknown bool - strictSemVer bool - ignoredDependencies IgnoredDependencies = make(map[string]bool) + filePath string + outdatedScope string + skipUnknown bool + strictSemVer bool + ignoredDependencies IgnoredDependencies = make(map[string]bool) + criticalDependencies CriticalDependencies = make(map[string]telescope.OutdatedScope) ) func init() { @@ -39,12 +78,13 @@ func init() { flag.BoolVar(&skipUnknown, "skip-unknown", false, "skip dependencies with unknown versions") flag.BoolVar(&strictSemVer, "strict-semver", false, "parse dependencies file with strict SemVer format") flag.Var(&ignoredDependencies, "i", "ignore specific dependency") + flag.Var(&criticalDependencies, "c", "highlight critical dependency") flag.Usage = usage } func usage() { - fmt.Fprintf(os.Stderr, "Usage: telescope [-f file_path] [-s outdated_scope] [-i ignored_dependency] [--skip-unknown] [--strict-semver]\n") + fmt.Fprintf(os.Stderr, "Usage: telescope [-f file_path] [-s outdated_scope] [-i ignored_dependency] [-c critical_dependency] [--skip-unknown] [--strict-semver]\n") flag.PrintDefaults() } @@ -52,9 +92,12 @@ func main() { flag.Parse() - atlas := telescope.NewAtlas(filePath, strictSemVer, ignoredDependencies) - atlas.ReportOutdated( + atlas := telescope.NewAtlas(filePath, strictSemVer, ignoredDependencies, criticalDependencies) + criticalFound := atlas.ReportOutdated( telescope.OutdatedScopeStrToEnum(outdatedScope), skipUnknown, ) + if criticalFound { + os.Exit(1) + } } diff --git a/telescope/atlas.go b/telescope/atlas.go index 4a61633..22d86c2 100644 --- a/telescope/atlas.go +++ b/telescope/atlas.go @@ -24,15 +24,13 @@ func (l Language) String() string { } type IReportable interface { - sortLexicographically() - queryVersionsInformation() - buildOutdatedMap() - ReportOutdated(scope OutdatedScope, skipUnknown bool) + ReportOutdated(scope OutdatedScope, skipUnknown bool) bool } type Atlas struct { name string language Language + criticalMap map[string]OutdatedScope dependencies []IDependable outdatedMap map[OutdatedScope][]IDependable } @@ -47,7 +45,12 @@ type PoetryLock struct { Packages []PoetryLockPackage `toml:"package"` } -func NewAtlas(filePath string, strictSemVer bool, ignoredDeps map[string]bool) IReportable { +func NewAtlas( + filePath string, + strictSemVer bool, + ignoredDeps map[string]bool, + criticalDeps map[string]OutdatedScope, +) IReportable { var atlas IReportable @@ -57,15 +60,15 @@ func NewAtlas(filePath string, strictSemVer bool, ignoredDeps map[string]bool) I switch fileName { case "go.mod": - atlas = buildAtlasGoMod(fileBytes, strictSemVer, ignoredDeps) + atlas = buildAtlasGoMod(fileBytes, strictSemVer, ignoredDeps, criticalDeps) case "poetry.lock": - atlas = buildAtlasPoetryLock(fileBytes, strictSemVer, ignoredDeps) + atlas = buildAtlasPoetryLock(fileBytes, strictSemVer, ignoredDeps, criticalDeps) default: panic(fmt.Errorf("unknown dep file: %s", filePath)) } - atlas.sortLexicographically() - atlas.queryVersionsInformation() - atlas.buildOutdatedMap() + atlas.(*Atlas).sortLexicographically() + atlas.(*Atlas).queryVersionsInformation() + atlas.(*Atlas).buildOutdatedMap() return atlas } @@ -80,7 +83,7 @@ func parseDependenciesFile(filePath string) []byte { return fileBytes } -func buildAtlasGoMod(fileBytes []byte, strictSemVer bool, ignoredDeps map[string]bool) IReportable { +func buildAtlasGoMod(fileBytes []byte, strictSemVer bool, ignoredDeps map[string]bool, criticalDeps map[string]OutdatedScope) IReportable { modObject, err := modfile.Parse("go.mod", fileBytes, nil) if err != nil { @@ -90,6 +93,7 @@ func buildAtlasGoMod(fileBytes []byte, strictSemVer bool, ignoredDeps map[string atlas := Atlas{ name: modObject.Module.Mod.Path, language: GO, + criticalMap: criticalDeps, dependencies: []IDependable{}, } for _, require := range modObject.Require { @@ -103,7 +107,7 @@ func buildAtlasGoMod(fileBytes []byte, strictSemVer bool, ignoredDeps map[string return &atlas } -func buildAtlasPoetryLock(fileBytes []byte, strictSemVer bool, ignoredDeps map[string]bool) IReportable { +func buildAtlasPoetryLock(fileBytes []byte, strictSemVer bool, ignoredDeps map[string]bool, criticalDeps map[string]OutdatedScope) IReportable { var poetryLock PoetryLock err := toml.Unmarshal(fileBytes, &poetryLock) @@ -115,6 +119,7 @@ func buildAtlasPoetryLock(fileBytes []byte, strictSemVer bool, ignoredDeps map[s name: "", language: PYTHON, dependencies: []IDependable{}, + criticalMap: criticalDeps, outdatedMap: map[OutdatedScope][]IDependable{}, } for _, pkg := range poetryLock.Packages { @@ -177,18 +182,22 @@ func (a *Atlas) buildOutdatedMap() { a.outdatedMap = outdatedMap } -func (a *Atlas) ReportOutdated(desiredScope OutdatedScope, skipUnknown bool) { +func (a *Atlas) ReportOutdated(desiredScope OutdatedScope, skipUnknown bool) bool { + + var criticalFound bool for _, scp := range [3]OutdatedScope{MAJOR, MINOR, PATCH} { if scp > desiredScope { break } color := MapScopeColor[scp] - a.reportByScope(scp, color) + criticalFound = a.reportByScope(scp, color) || criticalFound } if !skipUnknown { a.reportUnknownDependencies() } + + return criticalFound } func buildReportItem(dep IDependable) string { @@ -208,10 +217,15 @@ func buildReportItem(dep IDependable) string { ) } -func (a *Atlas) reportByScope(scope OutdatedScope, color int) { +func (a *Atlas) reportByScope(scope OutdatedScope, color int) bool { + + var criticalFound, criticalAnyway bool + if scp, ok := a.criticalMap["*"]; ok { + criticalAnyway = scp >= scope + } fmt.Printf( - "\033[%dm\n[ %d %s Version Outdated ]%s\n", + "\033[%dm\n[ %d %s Version Outdated ]%s\n\n", color, len(a.outdatedMap[scope]), scope.String(), @@ -220,10 +234,20 @@ func (a *Atlas) reportByScope(scope OutdatedScope, color int) { if len(a.outdatedMap[scope]) == 0 { fmt.Printf("no outdated dependencies") } + for _, dep := range a.outdatedMap[scope] { - fmt.Println(buildReportItem(dep)) + + scp, ok := a.criticalMap[dep.(*Dependency).Name] + if criticalAnyway || (ok && scp >= scope) { + criticalFound = criticalFound || true + fmt.Printf("* %s\n", buildReportItem(dep)) + } else { + fmt.Printf(" %s\n", buildReportItem(dep)) + } } fmt.Print("\n\033[0m") + + return criticalFound } func (a *Atlas) reportUnknownDependencies() { @@ -232,11 +256,11 @@ func (a *Atlas) reportUnknownDependencies() { return } fmt.Printf( - "\n[ %d UNKNOWN dependencies ]%s\n", + "\n[ %d UNKNOWN dependencies ]%s\n\n", len(a.outdatedMap[UNKNOWN]), strings.Repeat("=", 40), ) for _, dep := range a.outdatedMap[UNKNOWN] { - fmt.Println(buildReportItem(dep)) + fmt.Printf(" %s\n", buildReportItem(dep)) } } diff --git a/telescope/scope.go b/telescope/scope.go index 1f68fd1..12b81ea 100644 --- a/telescope/scope.go +++ b/telescope/scope.go @@ -1,6 +1,7 @@ package telescope import ( + "errors" "fmt" "strings" ) @@ -8,32 +9,49 @@ import ( type OutdatedScope int const ( - MAJOR OutdatedScope = iota + UP_TO_DATE OutdatedScope = iota + MAJOR MINOR PATCH - UP_TO_DATE UNKNOWN ) +var OutdatedScopeLiteral [5]string = [...]string{"UP_TO_DATE", "MAJOR", "MINOR", "PATCH", "UNKNOWN"} + var MapScopeColor map[OutdatedScope]int = map[OutdatedScope]int{ + UP_TO_DATE: 92, MAJOR: 91, MINOR: 93, PATCH: 94, - UP_TO_DATE: 92, UNKNOWN: 97, } func (o OutdatedScope) String() string { - return [...]string{"MAJOR", "MINOR", "PATCH", "UP_TO_DATE", "UNKNOWN"}[o] + return OutdatedScopeLiteral[o] } func OutdatedScopeStrToEnum(scopeStr string) OutdatedScope { scopeStr = strings.ToUpper(scopeStr) - for idx, scp := range [...]string{"MAJOR", "MINOR", "PATCH"} { + for idx, scp := range OutdatedScopeLiteral { if scopeStr == scp { return OutdatedScope(idx) } } panic(fmt.Errorf("unknown scope %s", scopeStr)) } + +func GetTopScope(scopes []OutdatedScope) OutdatedScope { + + if len(scopes) == 0 { + panic(errors.New("should provide at least one item")) + } + + var hold OutdatedScope = UNKNOWN + for _, scp := range scopes { + if scp < hold { + hold = scp + } + } + return hold +}