github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/repo/list/list.go (about) 1 package list 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 "time" 8 9 "github.com/cli/cli/api" 10 "github.com/cli/cli/internal/config" 11 "github.com/cli/cli/pkg/cmdutil" 12 "github.com/cli/cli/pkg/iostreams" 13 "github.com/cli/cli/pkg/text" 14 "github.com/cli/cli/utils" 15 "github.com/spf13/cobra" 16 ) 17 18 type ListOptions struct { 19 HttpClient func() (*http.Client, error) 20 Config func() (config.Config, error) 21 IO *iostreams.IOStreams 22 Exporter cmdutil.Exporter 23 24 Limit int 25 Owner string 26 27 Visibility string 28 Fork bool 29 Source bool 30 Language string 31 Topic string 32 Archived bool 33 NonArchived bool 34 35 Now func() time.Time 36 } 37 38 func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command { 39 opts := ListOptions{ 40 IO: f.IOStreams, 41 Config: f.Config, 42 HttpClient: f.HttpClient, 43 Now: time.Now, 44 } 45 46 var ( 47 flagPublic bool 48 flagPrivate bool 49 ) 50 51 cmd := &cobra.Command{ 52 Use: "list [<owner>]", 53 Args: cobra.MaximumNArgs(1), 54 Short: "List repositories owned by user or organization", 55 RunE: func(c *cobra.Command, args []string) error { 56 if opts.Limit < 1 { 57 return &cmdutil.FlagError{Err: fmt.Errorf("invalid limit: %v", opts.Limit)} 58 } 59 60 if flagPrivate && flagPublic { 61 return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--public` or `--private`")} 62 } 63 if opts.Source && opts.Fork { 64 return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--source` or `--fork`")} 65 } 66 if opts.Archived && opts.NonArchived { 67 return &cmdutil.FlagError{Err: fmt.Errorf("specify only one of `--archived` or `--no-archived`")} 68 } 69 70 if flagPrivate { 71 opts.Visibility = "private" 72 } else if flagPublic { 73 opts.Visibility = "public" 74 } 75 76 if len(args) > 0 { 77 opts.Owner = args[0] 78 } 79 80 if runF != nil { 81 return runF(&opts) 82 } 83 return listRun(&opts) 84 }, 85 } 86 87 cmd.Flags().IntVarP(&opts.Limit, "limit", "L", 30, "Maximum number of repositories to list") 88 cmd.Flags().BoolVar(&flagPrivate, "private", false, "Show only private repositories") 89 cmd.Flags().BoolVar(&flagPublic, "public", false, "Show only public repositories") 90 cmd.Flags().BoolVar(&opts.Source, "source", false, "Show only non-forks") 91 cmd.Flags().BoolVar(&opts.Fork, "fork", false, "Show only forks") 92 cmd.Flags().StringVarP(&opts.Language, "language", "l", "", "Filter by primary coding language") 93 cmd.Flags().StringVar(&opts.Topic, "topic", "", "Filter by topic") 94 cmd.Flags().BoolVar(&opts.Archived, "archived", false, "Show only archived repositories") 95 cmd.Flags().BoolVar(&opts.NonArchived, "no-archived", false, "Omit archived repositories") 96 cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.RepositoryFields) 97 98 return cmd 99 } 100 101 var defaultFields = []string{"nameWithOwner", "description", "isPrivate", "isFork", "isArchived", "createdAt", "pushedAt"} 102 103 func listRun(opts *ListOptions) error { 104 httpClient, err := opts.HttpClient() 105 if err != nil { 106 return err 107 } 108 109 filter := FilterOptions{ 110 Visibility: opts.Visibility, 111 Fork: opts.Fork, 112 Source: opts.Source, 113 Language: opts.Language, 114 Topic: opts.Topic, 115 Archived: opts.Archived, 116 NonArchived: opts.NonArchived, 117 Fields: defaultFields, 118 } 119 if opts.Exporter != nil { 120 filter.Fields = opts.Exporter.Fields() 121 } 122 123 cfg, err := opts.Config() 124 if err != nil { 125 return err 126 } 127 128 host, err := cfg.DefaultHost() 129 if err != nil { 130 return err 131 } 132 133 listResult, err := listRepos(httpClient, host, opts.Limit, opts.Owner, filter) 134 if err != nil { 135 return err 136 } 137 138 if err := opts.IO.StartPager(); err != nil { 139 fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err) 140 } 141 defer opts.IO.StopPager() 142 143 if opts.Exporter != nil { 144 return opts.Exporter.Write(opts.IO, listResult.Repositories) 145 } 146 147 cs := opts.IO.ColorScheme() 148 tp := utils.NewTablePrinter(opts.IO) 149 now := opts.Now() 150 151 for _, repo := range listResult.Repositories { 152 info := repoInfo(repo) 153 infoColor := cs.Gray 154 if repo.IsPrivate { 155 infoColor = cs.Yellow 156 } 157 158 t := repo.PushedAt 159 if repo.PushedAt == nil { 160 t = &repo.CreatedAt 161 } 162 163 tp.AddField(repo.NameWithOwner, nil, cs.Bold) 164 tp.AddField(text.ReplaceExcessiveWhitespace(repo.Description), nil, nil) 165 tp.AddField(info, nil, infoColor) 166 if tp.IsTTY() { 167 tp.AddField(utils.FuzzyAgoAbbr(now, *t), nil, cs.Gray) 168 } else { 169 tp.AddField(t.Format(time.RFC3339), nil, nil) 170 } 171 tp.EndRow() 172 } 173 174 if opts.IO.IsStdoutTTY() { 175 hasFilters := filter.Visibility != "" || filter.Fork || filter.Source || filter.Language != "" || filter.Topic != "" 176 title := listHeader(listResult.Owner, len(listResult.Repositories), listResult.TotalCount, hasFilters) 177 fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title) 178 } 179 180 return tp.Render() 181 } 182 183 func listHeader(owner string, matchCount, totalMatchCount int, hasFilters bool) string { 184 if totalMatchCount == 0 { 185 if hasFilters { 186 return "No results match your search" 187 } else if owner != "" { 188 return "There are no repositories in @" + owner 189 } 190 return "No results" 191 } 192 193 var matchStr string 194 if hasFilters { 195 matchStr = " that match your search" 196 } 197 return fmt.Sprintf("Showing %d of %d repositories in @%s%s", matchCount, totalMatchCount, owner, matchStr) 198 } 199 200 func repoInfo(r api.Repository) string { 201 var tags []string 202 203 if r.IsPrivate { 204 tags = append(tags, "private") 205 } else { 206 tags = append(tags, "public") 207 } 208 if r.IsFork { 209 tags = append(tags, "fork") 210 } 211 if r.IsArchived { 212 tags = append(tags, "archived") 213 } 214 215 return strings.Join(tags, ", ") 216 }