Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parsing output of Users.ListFollowers #1176

Closed
davidobrien1985 opened this issue May 26, 2019 · 4 comments
Closed

Parsing output of Users.ListFollowers #1176

davidobrien1985 opened this issue May 26, 2019 · 4 comments

Comments

@davidobrien1985
Copy link

Hi,

golang noob here.
Instead of using REST calls I thought I'd use this package here to interact with GitHub.

For example when using Users.ListFollowers() I get all the information back of all the followers.
For a subsequent step I only need the Login property (the GitHub user name).
However, I can't figure out how to work with the output of above call, the type being gitub.User.

How do I parse this output?

Thanks all!

@gmlewis
Copy link
Collaborator

gmlewis commented May 26, 2019

Hi @davidobrien1985, and welcome to Go!
I predict that if you give it a chance, Go will become one of your favorite (dare I say "go-to"?) programming languages!

Pro-tip 1: If you want to get really good at Go really fast, dig into solving problems on a website like https://codingame.com or http://adventofcode.com.

Pro-tip 2: I used to be a snob about IDEs and how they were only for the weak, but VSCode has truly world-class support for Go and I highly recommend using it.

Now, to answer your question, the auto-generated Godocs are a great place to start. Here's the call you are asking about:
https://godoc.org/github.com/google/go-github/github#UsersService.ListFollowers

You'll see that it returns a slice of []*User and if you click on that, you see the User struct:
https://godoc.org/github.com/google/go-github/github#User

Due to the nature of how JSON is handled in Go, we typically use pointers to fields so that we can use omitempty and the fields will be nil if not supplied. As a result, we added "accessors" to make accessing the fields much easier... which you will see below that struct.

So if you only needed the Login property of each user (and optionally wanted to print their name), you could do something like this...

users, _, err := s.ListFollowers(ctx, "gmlewis", nil)
if err != nil { /* handle the error */ }
for _, user := range users {
  log.Printf("%v (%v)", user.GetLogin(), user.GetName())
}

Now actually, this is a simple but not complete example... GitHub has pagination... so if this user has a large number of followers, GitHub breaks up the response into many request/response pairs. I'll respond again later with a more complete example, but hopefully this will get you up-and-running quickly.

Have fun with Go, and feel free to ask questions any time.

@gmlewis
Copy link
Collaborator

gmlewis commented May 26, 2019

As promised, here is a complete example to list all the followers of a particular GitHub user:

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

const (
	maxPerPage = 1000
)

var (
	followed = flag.String("user", "gmlewis", "GitHub user to query")
)

type githubClient struct {
	client *github.Client
}

func main() {
	flag.Parse()

	tc := oauth2.NewClient(oauth2.NoContext, nil)
	ghc := &githubClient{
		client: github.NewClient(tc),
	}

	followers, err := ghc.followers(*followed)
	if err != nil {
		log.Fatalf("ListFollowers(%v): %v", *followed, err)
	}

	sort.Strings(followers)
	fmt.Printf("%v followers of GitHub user %q: %v", len(followers), *followed, strings.Join(followers, ", "))
}

func (g *githubClient) followers(username string) ([]string, error) {
	ctx := context.Background()

	opt := &github.ListOptions{PerPage: maxPerPage}
	var result []string
	for {
		users, _, err := g.client.Users.ListFollowers(ctx, username, opt)
		if err != nil {
			return nil, err
		}
		if len(users) == 0 {
			break
		}
		for _, user := range users {
			result = append(result, user.GetLogin())
		}
		opt.Page++
	}
	return result, nil
}

Note that GitHub has API query rate limits... which you will quickly become an expert at if you hit their API too hard. I believe the limit is 5000 queries per hour if your program uses an authenticated user, otherwise the rate limit is something like 60 per hour. The program above uses unauthenticated requests.

I hope that helps. Please let me know if you have any questions.

(Also note that this example is not optimal... it makes one extra API call when it could take a look at the response header to see if there are actually more users before making the last API call... but for simplicity I make the extra API call above. Let me know if you really want to see an optimized version.)

@gmlewis
Copy link
Collaborator

gmlewis commented May 26, 2019

Here's a better example that shouldn't call the API one extra time.
Note that I've seen some weirdness when playing with the PerPage value. 1000 seems to be the maximum value, but setting it to smaller numbers then increasing the Page number will sometimes get you the same results, so be warned you might need to sanitize the results or experiment if you don't use 1000 for PerPage.

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"sort"
	"strings"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

const (
	maxPerPage = 1000
)

var (
	followed = flag.String("user", "gmlewis", "GitHub user to query")
)

type githubClient struct {
	client *github.Client
}

func main() {
	flag.Parse()

	tc := oauth2.NewClient(oauth2.NoContext, nil)
	ghc := &githubClient{
		client: github.NewClient(tc),
	}

	followers, err := ghc.followers(*followed)
	if err != nil {
		log.Fatalf("ListFollowers(%v): %v", *followed, err)
	}

	sort.Strings(followers)
	fmt.Printf("%v followers of GitHub user %q: %v", len(followers), *followed, strings.Join(followers, ", "))
}

func (g *githubClient) followers(username string) ([]string, error) {
	ctx := context.Background()

	opt := &github.ListOptions{PerPage: maxPerPage}
	var result []string
	for {
		log.Printf("Calling GitHub: %#v", *opt)
		users, resp, err := g.client.Users.ListFollowers(ctx, username, opt)
		if err != nil {
			return nil, err
		}
		log.Printf("Got %v users, resp.NextPage=%v, resp.Header['Link']=%v", len(users), resp.NextPage, resp.Header["Link"])
		for _, user := range users {
			result = append(result, user.GetLogin())
		}
		opt.Page++
		if opt.Page >= resp.NextPage {
			break
		}
	}
	return result, nil
}

@gmlewis
Copy link
Collaborator

gmlewis commented May 26, 2019

I'm going to go ahead and close this issue. Feel free to reopen if you have any questions or if I misunderstood your original question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants