github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/dependencies/changesummary.go (about) 1 package dependencies 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 "strings" 8 9 "github.com/ActiveState/cli/internal/locale" 10 "github.com/ActiveState/cli/internal/logging" 11 "github.com/ActiveState/cli/internal/output" 12 "github.com/ActiveState/cli/internal/sliceutils" 13 "github.com/ActiveState/cli/pkg/buildplan" 14 ) 15 16 // showUpdatedPackages specifies whether or not to include updated dependencies in the direct 17 // dependencies list, and whether or not to include updated dependencies when calculating indirect 18 // dependency numbers. 19 const showUpdatedPackages = true 20 21 // OutputChangeSummary looks over the given artifact changeset and attempts to determine if a single 22 // package install request was made. If so, it computes and lists the additional dependencies being 23 // installed for that package. 24 // `artifacts` is an ArtifactMap containing artifacts in the changeset, and `filter` contains any 25 // runtime requirements/artifacts already installed. 26 func OutputChangeSummary(out output.Outputer, changeset *buildplan.ArtifactChangeset, alreadyInstalled buildplan.Artifacts) { 27 addedString := []string{} 28 addedLocale := []string{} 29 added := buildplan.Ingredients{} 30 dependencies := buildplan.Ingredients{} 31 directDependencies := buildplan.Ingredients{} 32 for _, a := range changeset.Added { 33 added = append(added, a.Ingredients...) 34 for _, i := range a.Ingredients { 35 v := fmt.Sprintf("%s@%s", i.Name, i.Version) 36 addedString = append(addedLocale, v) 37 addedLocale = append(addedLocale, fmt.Sprintf("[ACTIONABLE]%s[/RESET]", v)) 38 dependencies = append(dependencies, i.RuntimeDependencies(true)...) 39 directDependencies = append(dependencies, i.RuntimeDependencies(false)...) 40 } 41 } 42 43 dependencies = sliceutils.UniqueByProperty(dependencies, func(i *buildplan.Ingredient) any { return i.IngredientID }) 44 directDependencies = sliceutils.UniqueByProperty(directDependencies, func(i *buildplan.Ingredient) any { return i.IngredientID }) 45 numIndirect := len(dependencies) - len(directDependencies) 46 47 sort.SliceStable(directDependencies, func(i, j int) bool { 48 return directDependencies[i].Name < directDependencies[j].Name 49 }) 50 51 logging.Debug("packages %s have %d direct dependencies and %d indirect dependencies", 52 strings.Join(addedString, ", "), len(directDependencies), numIndirect) 53 if len(directDependencies) == 0 { 54 return 55 } 56 57 // Process the existing runtime requirements into something we can easily compare against. 58 oldRequirements := alreadyInstalled.Ingredients().ToIDMap() 59 60 localeKey := "additional_dependencies" 61 if numIndirect > 0 { 62 localeKey = "additional_total_dependencies" 63 } 64 out.Notice(locale.Tr(localeKey, strings.Join(addedLocale, ", "), strconv.Itoa(len(directDependencies)), strconv.Itoa(numIndirect))) 65 66 // A direct dependency list item is of the form: 67 // ├─ name@version (X dependencies) 68 // or 69 // └─ name@oldVersion → name@newVersion (Updated) 70 // depending on whether or not it has subdependencies, and whether or not showUpdatedPackages is 71 // `true`. 72 for i, ingredient := range directDependencies { 73 prefix := "├─" 74 if i == len(directDependencies)-1 { 75 prefix = "└─" 76 } 77 78 ingredientDeps := ingredient.RuntimeDependencies(true) 79 subdependencies := "" 80 if numSubs := len(ingredientDeps); numSubs > 0 { 81 subdependencies = fmt.Sprintf(" ([ACTIONABLE]%s[/RESET] dependencies)", // intentional leading space 82 strconv.Itoa(numSubs)) 83 } 84 85 item := fmt.Sprintf("[ACTIONABLE]%s@%s[/RESET]%s", // intentional omission of space before last %s 86 ingredient.Name, ingredient.Version, subdependencies) 87 oldVersion, exists := oldRequirements[ingredient.IngredientID] 88 if exists && ingredient.Version != "" && oldVersion.Version != ingredient.Version { 89 item = fmt.Sprintf("[ACTIONABLE]%s@%s[/RESET] → %s (%s)", oldVersion.Name, oldVersion.Version, item, locale.Tl("updated", "updated")) 90 } 91 92 out.Notice(fmt.Sprintf(" [DISABLED]%s[/RESET] %s", prefix, item)) 93 } 94 95 out.Notice("") // blank line 96 }