#go

January 27, 2017

Build your own ASCII github issue table in Go!

Introduction

In this blogpost I’m going to show you how you can create your own github issue table for inside your terminal.
We will be covering some cool features and the implementation.

Code can be found here: Github repo

The looks of the application

Programming

What we want to achieve with the application is that we can specify a github repository url somehow and display x amount of issues in a pretty printed table. Since our application is going to be a command line application we need some way to retrieve the input from the user. For this we specify 2 flags:

var (
    amount = flag.Int("amount", -1, "Set the maximum amount of issues to show")
    repo   = flag.String("repo", "", "Repository on http://www.github.com to list the issues from")
)

This is the only input our application need (for now). After the user have filled in our flags we need a way to parse that input.

u, err := url.Parse(githubRepoURL)  
if err != nil {
	fmt.Printf("Invalid url! %s", err)
	os.Exit(1)
}   
path := strings.Split(u.EscapedPath(), "/") 
if len(path) < 3 {
	fmt.Println("Invalid github URL")
	os.Exit(1)
}   

Example github url path: /barthr/issuestable
Here we parse the url and we split it on the "/". If the path contains less than 3 items the url is invalid, this means there isn’t a repository name and a repository owner. With this information we can retrieve the issues from the github API. For this we use the Go github client. This client library provides us with methods for retrieving issues from repository’s.

Lets do this!

// New github client
client = github.NewClient(nil)

// Initiate empty options with 100 records to fetch each page (maximum)
opt := &github.IssueListByRepoOptions{
    ListOptions: github.ListOptions{PerPage: 100},
}

var issues []*github.Issue
// Check for amount less than 0, if that is the case (no amount specified) we fetch all the issues
// Amount flag is -1 if not set (this way we keep looping until there aren't any pages left)
for len(issues) <= *amount || *amount < 0 {
    // Retrieve first batch of items
    newIssues, resp, err := client.Issues.ListByRepo(path[1], path[2], opt)
    if err != nil {
    	log.Fatalf("error fetching issues for %v/%v: %v", path[1], path[2], err)
    }
    // add them to the issue array
    issues = append(issues, newIssues...)
    // if the response contains no next page stop looping, we fetched all the issues
    if resp.NextPage == 0 {
    	break
    }
    // Set the next page on the options passed to the ListByRepo call
    opt.ListOptions.Page = resp.NextPage
}

First we initiate a issue array. This will be the array which holds the issues we want to show. Because we can’t get all the issues at once from the github api, we have to add pagination to fetch the remaining ones. Luckily the github api client supports this. The ListByRepo call also returns a page object which holds the next page. We keep looping until we have the amount specified by the flag. If no amount value is given we keep looping until we have all the issues

We can now check if we have any issues and if we don’t we shouldn’t be writing a table

if len(issues) == 0 {
    fmt.Printf("No issues found for this repository")
    os.Exit(0)
}

Finally we can draw the table for our issues. The things we want to show are the index, number of the issue, created_at and the title. We will be using a library for drawing the ASCII table Go tablewriter

// Create a new tablewriter to Stdout
table := tablewriter.NewWriter(os.Stdout)
// Set the headers we want
table.SetHeader([]string{"index", "number", "created_at", "title"})

// Loop over the issues 
for index, issue := range issues {
    // if the index == the amount flag than we need to stop looping, the desired result is there
    if index == *amount {
    	break
    }
    // Create a row for the tablewriter (the tablewriter expects a string array)
    // We convert all of our variables to a string
    row := make([]string, 4)
    row[0] = strconv.Itoa(index)
    row[1] = strconv.Itoa(*issue.Number)
    row[2] = issue.CreatedAt.String()
    row[3] = *issue.Title
    table.Append(row)
}
// Set a row line
table.SetRowLine(true)
// Render it to the screen
table.Render()

We can use the application like this:

issuestable -repo github.com/golang/go -amount 100

Conclusion

With a few lines of code (and with the help of a few library’s) we can easily create our custom ASCII table displaying github issues for the repo we want. This is cool, but a little bit boring at the moment. It would be nice if we can also specify custom time events and maybe even issue tags. This is all very easy with the github client library and we can easily add more items to our ascii table. You could even take this further and make a whole interactive issue browser inside your terminal. I will leave that one as a exercise for the reader :).

Thanks all for reading my blog post!

© Bart Fokker 2017