func (l *Lister) DeriveRepoFullName(partial string) (repo string) {
	return fmt.Sprintf("%s/%s", l.user, partial)
}
DeriveRepoFullName derives the full name of a repo from a partial name.
package github
import (
	"context"
	stderrs "errors"
	"fmt"
	"github.com/cockroachdb/errors"
	"github.com/google/go-github/v27/github"
	"golang.org/x/sync/errgroup"
	"golang.org/x/sync/semaphore"
	"go.stevenxie.me/api/pkg/zero"
	"go.stevenxie.me/vaingogh/repo"
)
func NewLister(
	c *github.Client,
	username string,
	opts ...func(*ListerConfig),
) *Lister {
	cfg := ListerConfig{
		Concurrency: 5,
	}
	for _, opt := range opts {
		opt(&cfg)
	}
	return &Lister{
		client:      c,
		user:        username,
		concurrency: cfg.Concurrency,
	}
}
type (
	Lister struct {
		client      *github.Client
		concurrency int
		user    string
		checked bool
		isOrg   bool
	}
	ListerConfig struct {
		Concurrency int
	}
)
var _ repo.ListerService = (*Lister)(nil)
func (l *Lister) ListGoRepos() ([]string, error) {
	if err := l.checkUser(); err != nil {
		return nil, errors.Wrap(err, "github: checking user type")
	}
	var (
		repos []*github.Repository
		err   error
	)
	if l.isOrg {
		repos, _, err = l.client.Repositories.ListByOrg(
			context.Background(),
			l.user,
			&github.RepositoryListByOrgOptions{Type: "public"},
		)
	} else {
		repos, _, err = l.client.Repositories.List(
			context.Background(),
			l.user,
			&github.RepositoryListOptions{
				Visibility:  "public",
				Affiliation: "owner",
				Sort:        "updated",
			},
		)
	}
	if err != nil {
		return nil, err
	}
	var (
		results = make(chan string)
		gorepos = make([]string, 0, len(repos))
		done    = make(chan zero.Struct)
	)
	go func(results <-chan string, done chan<- zero.Struct) {
		for result := range results {
			gorepos = append(gorepos, result)
		}
		done <- zero.Empty()
	}(results, done)
	numWorkers := l.concurrency
	if len(repos) < numWorkers {
		numWorkers = len(repos)
	}
	var (
		sem             = semaphore.NewWeighted(int64(numWorkers))
		group, groupctx = errgroup.WithContext(context.Background())
	)
	for _, repo := range repos {
		if err = sem.Acquire(groupctx, 1); err != nil {
			return nil, errors.Wrap(err, "acquiring semaphore")
		}
		func(repo *github.Repository, svc *github.RepositoriesService) {
			group.Go(func() error {
				defer sem.Release(1)
				languages, _, err := svc.ListLanguages(
					groupctx,
					repo.GetOwner().GetLogin(),
					repo.GetName(),
				)
				if err != nil {
					return errors.Wrapf(err, "listing languages for '%s'",
						repo.GetFullName())
				}
				if _, ok := languages["Go"]; ok {
					results <- repo.GetFullName()
				}
				return nil
			})
		}(repo, l.client.Repositories)
	}
	if err = group.Wait(); err != nil {
		return nil, errors.Wrap(err, "github: checking languages")
	}
	close(results)
	<-done
	return gorepos, nil
}
func (l *Lister) checkUser() error {
	if l.checked {
		return nil
	}
	{
		user, _, err := l.client.Users.Get(context.Background(), l.user)
		if err != nil {
			return errors.Wrap(err, "getting user details")
		}
		if user != nil {
			goto Checked
		}
	}
	{
		org, _, err := l.client.Organizations.Get(context.Background(), l.user)
		if err != nil {
			return errors.Wrap(err, "getting org details")
		}
		if org != nil {
			l.isOrg = true
			goto Checked
		}
	}
	return ErrUserNotExists
Checked:
	l.checked = true
	return nil
}
var ErrUserNotExists = stderrs.New("no such user or organization exists")