github.com/dnephin/dobi@v0.15.0/cmd/list.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "sort" 6 7 "strings" 8 9 "github.com/dnephin/dobi/config" 10 "github.com/dnephin/dobi/logging" 11 "github.com/spf13/cobra" 12 ) 13 14 type listOptions struct { 15 all bool 16 grouped bool 17 tags []string 18 } 19 20 func (o listOptions) tagMatch(tags []string) bool { 21 for _, otag := range o.tags { 22 for _, tag := range tags { 23 if tag == otag { 24 return true 25 } 26 } 27 } 28 return false 29 } 30 31 func newListCommand(opts *dobiOptions) *cobra.Command { 32 var listOpts listOptions 33 cmd := &cobra.Command{ 34 Use: "list", 35 Short: "List resources", 36 RunE: func(cmd *cobra.Command, args []string) error { 37 return runList(opts, listOpts) 38 }, 39 } 40 flags := cmd.Flags() 41 flags.BoolVarP( 42 &listOpts.all, "all", "a", false, 43 "List all resources, including those without descriptions") 44 flags.BoolVarP( 45 &listOpts.grouped, "grouped", "g", false, 46 "List resources grouped by tag. Only resources "+ 47 "with configured tags will be listed.") 48 flags.StringSliceVarP( 49 &listOpts.tags, "tags", "t", nil, 50 "List tasks matching the tag") 51 return cmd 52 } 53 54 func runList(opts *dobiOptions, listOpts listOptions) error { 55 conf, err := config.Load(opts.filename) 56 if err != nil { 57 return err 58 } 59 60 tags := getTags(conf.Resources) 61 var descriptions []string 62 if listOpts.grouped { 63 resources := filterResourcesTags(conf, listOpts) 64 descriptions = getDescriptionsByTag(resources) 65 } else { 66 resources := filterResources(conf, listOpts) 67 descriptions = getDescriptions(resources) 68 } 69 70 if len(descriptions) == 0 { 71 logging.Log.Warn("No resources found. Try --all or --tags.") 72 return nil 73 } 74 fmt.Print(format(descriptions, tags)) 75 return nil 76 } 77 78 func filterResourcesTags(conf *config.Config, listOpts listOptions) []resourceGroup { 79 tags := []resourceGroup{} 80 if listOpts.all { 81 // Add 'none' tag group accessible via [0] for resources with no tags configured 82 tags = append(tags, resourceGroup{tag: "none"}) 83 } 84 for _, name := range conf.Sorted() { 85 res := conf.Resources[name] 86 if len(res.CategoryTags()) > 0 { 87 for _, tagname := range res.CategoryTags() { 88 currentGroupIndex := 0 89 if i, found := findGroup(tags, tagname); found { 90 currentGroupIndex = i 91 } else { 92 tags = append(tags, resourceGroup{ 93 tag: tagname, 94 }) 95 currentGroupIndex = len(tags) - 1 96 } 97 tags[currentGroupIndex].resources = append(tags[currentGroupIndex].resources, 98 namedResource{ 99 name: name, 100 resource: res, 101 }) 102 } 103 } else { 104 if listOpts.all { 105 tags[0].resources = append(tags[0].resources, 106 namedResource{ 107 name: name, 108 resource: res, 109 }) 110 } 111 } 112 } 113 114 return tags 115 } 116 117 func filterResources(conf *config.Config, listOpts listOptions) []namedResource { 118 resources := []namedResource{} 119 for _, name := range conf.Sorted() { 120 res := conf.Resources[name] 121 if include(res, listOpts) { 122 resources = append(resources, namedResource{name: name, resource: res}) 123 } 124 } 125 return resources 126 } 127 128 type resourceGroup struct { 129 tag string 130 resources []namedResource 131 } 132 133 type namedResource struct { 134 name string 135 resource config.Resource 136 } 137 138 func (n namedResource) Describe() string { 139 desc := n.resource.Describe() 140 if desc == "" { 141 return n.resource.String() 142 } 143 return desc 144 } 145 146 func include(res config.Resource, listOpts listOptions) bool { 147 if listOpts.all || listOpts.tagMatch(res.CategoryTags()) { 148 return true 149 } 150 return len(listOpts.tags) == 0 && res.Describe() != "" 151 } 152 153 func getDescriptions(resources []namedResource) []string { 154 lines := []string{} 155 for _, named := range resources { 156 line := fmt.Sprintf("%-20s %s", named.name, named.Describe()) 157 lines = append(lines, line) 158 } 159 return lines 160 } 161 162 func getDescriptionsByTag(resources []resourceGroup) []string { 163 lines := []string{} 164 for _, tag := range resources { 165 descriptions := getDescriptions(tag.resources) 166 lines = append(lines, formatTags(tag.tag, descriptions)) 167 } 168 return lines 169 } 170 171 func getTags(resources map[string]config.Resource) []string { 172 mapped := make(map[string]struct{}) 173 for _, res := range resources { 174 for _, tag := range res.CategoryTags() { 175 mapped[tag] = struct{}{} 176 } 177 } 178 tags := []string{} 179 for tag := range mapped { 180 tags = append(tags, tag) 181 } 182 sort.Strings(tags) 183 return tags 184 } 185 186 func findGroup(slice []resourceGroup, tag string) (int, bool) { 187 for i, item := range slice { 188 if item.tag == tag { 189 return i, true 190 } 191 } 192 return -1, false 193 } 194 195 func format(descriptions []string, tags []string) string { 196 resources := strings.Join(descriptions, "\n ") 197 198 msg := fmt.Sprintf("Resources:\n %s\n", resources) 199 if len(tags) > 0 { 200 msg += fmt.Sprintf("\nTags:\n %s\n", strings.Join(tags, ", ")) 201 } 202 return msg 203 } 204 205 func formatTags(tag string, descriptions []string) string { 206 msg := fmt.Sprintf("Tag: %s\n", tag) 207 resources := strings.Join(descriptions, "\n ") 208 msg += fmt.Sprintf(" %s\n", resources) 209 return msg 210 }