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  }