Skip to content

Commit 4b25d9b

Browse files
authored
Merge pull request #1 from elimity-com/initial-version
Set up initial version
2 parents 3fa6cff + 9be8a36 commit 4b25d9b

File tree

5 files changed

+223
-0
lines changed

5 files changed

+223
-0
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Elimity backend internship exercise
2+
3+
Thank you for applying for the **Elimity Backend Developer Intern** position! This exercise is designed to give you an
4+
opportunity to show off your programming skills that would be relevant to work at Elimity, and to give you an idea of
5+
what type of work you would be doing during your internship. The code in this repository provides a Go module as a
6+
starting point for the assignments. It implements `gothub`, a simple CLI for tracking public GitHub repositories. The
7+
actual assignments are listed below.
8+
9+
## Installation
10+
11+
The `gothub` CLI can be installed using `go get` with Go 1.16+:
12+
13+
```sh
14+
$ go get github.com/elimity-com/backend-intern-exercise/cmd/gothub
15+
```
16+
17+
## Usage
18+
19+
```sh
20+
$ gothub help
21+
Simple CLI for tracking public GitHub repositories.
22+
23+
Usage:
24+
gothub help
25+
gothub track [-interval=<interval>]
26+
27+
Commands:
28+
help Show usage information
29+
track Track public GitHub repositories
30+
31+
Options:
32+
-interval=<interval> Repository update interval, greater than zero [default: 10s]
33+
```
34+
35+
## Assignments
36+
37+
Currently `gothub` only supports regularly printing the most recently updated public GitHub repositories. The
38+
assignments consist of extending its functionality. More specifically, we ask you to implement these improvements:
39+
40+
1. GitHub applies very strict rate limiting for anonymous requests, causing `gothub` to fail when it contacts the API
41+
too frequently. As stated [here](https://docs.github.com/en/rest/reference/search#rate-limit), the rate limit could
42+
be increased by using authenticated requests. Therefore we ask you to add a `token-file` option to the `gothub` CLI,
43+
which should support authenticating requests by reading a
44+
[GitHub personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)
45+
read from the given file path. If not provided, `gothub` should stick to anonymous requests.
46+
47+
2. Currently `gothub` only prints repository names. We ask you to extend this to a table-formatted output with columns
48+
for the repository's owner, name, last update timestamp and star count. If the repository belongs to an organization,
49+
list the organization's name in the owner column. The result should look like this (including headers):
50+
51+
```
52+
Owner | Name | Updated at (UTC) | Star count
53+
golang | go | 2021-03-08T11:29:52 | 12345
54+
google | go-github | 2021-03-07T12:34:56 | 5432
55+
angular | angular | 2021-02-29T05:43:21 | 43210
56+
```
57+
58+
3. The repositories outputted by `gothub` are only filtered by their accessibility, as they are all public. We ask you
59+
to add a `min-stars` option, which causes `gothub` to filter out repositories with a star count below the given
60+
value. If not provided, `gothub` should use a minimum star count of zero.
61+
62+
### Grading
63+
64+
Your solutions for the assignments will be graded according to the following points:
65+
66+
- compliance with the listed requirements,
67+
- consistency with the existing code,
68+
- understanding of Go fundamentals,
69+
- adherence to Elimity's style guidelines:
70+
- strong types over implicit assumptions,
71+
- immutability over mutability, and
72+
- simple over clever.
73+
74+
### Practicalities
75+
76+
- You can use any library you want, but we prefer the standard library.
77+
- These practices are important on the job, but won't affect your grades:
78+
- proper version control,
79+
- testing (which is complex for a CLI without business logic).

cmd/gothub/main.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"io/ioutil"
8+
"os"
9+
"path/filepath"
10+
"time"
11+
12+
"github.com/elimity-com/backend-intern-exercise/internal"
13+
)
14+
15+
var args = os.Args
16+
17+
var name = makeName()
18+
19+
func log(message string) {
20+
fmt.Fprintf(os.Stderr, "%s: %s\n", name, message)
21+
}
22+
23+
func main() {
24+
if err := run(); err != nil {
25+
message := err.Error()
26+
log(message)
27+
if _, ok := err.(usageError); ok {
28+
message := fmt.Sprintf("run '%s help' for usage information", name)
29+
log(message)
30+
}
31+
}
32+
}
33+
34+
func makeName() string {
35+
path := args[0]
36+
return filepath.Base(path)
37+
}
38+
39+
func parseInterval() (time.Duration, error) {
40+
set := flag.NewFlagSet("", flag.ContinueOnError)
41+
var interval time.Duration
42+
set.DurationVar(&interval, "interval", 10*time.Second, "")
43+
set.SetOutput(ioutil.Discard)
44+
args := args[2:]
45+
if err := set.Parse(args); err != nil {
46+
return 0, errors.New("got invalid flags")
47+
}
48+
if interval <= 0 {
49+
return 0, errors.New("got invalid interval")
50+
}
51+
return interval, nil
52+
}
53+
54+
func run() error {
55+
if nbArgs := len(args); nbArgs < 2 {
56+
return usageError{message: "missing command"}
57+
}
58+
switch args[1] {
59+
case "help":
60+
const usage = `
61+
Simple CLI for tracking public GitHub repositories.
62+
63+
Usage:
64+
%[1]s help
65+
%[1]s track [-interval=<interval>]
66+
67+
Commands:
68+
help Show usage information
69+
track Track public GitHub repositories
70+
71+
Options:
72+
-interval=<interval> Repository update interval, greater than zero [default: 10s]
73+
`
74+
fmt.Fprintf(os.Stdout, usage, name)
75+
return nil
76+
77+
case "track":
78+
interval, err := parseInterval()
79+
if err != nil {
80+
message := fmt.Sprintf("failed parsing interval: %v", err)
81+
return usageError{message: message}
82+
}
83+
if err := internal.Track(interval); err != nil {
84+
return fmt.Errorf("failed tracking: %v", err)
85+
}
86+
return nil
87+
88+
default:
89+
return usageError{message: "got invalid command"}
90+
}
91+
}
92+
93+
type usageError struct {
94+
message string
95+
}
96+
97+
func (e usageError) Error() string {
98+
return e.message
99+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/elimity-com/backend-intern-exercise
2+
3+
go 1.16
4+
5+
require github.com/google/go-github/v33 v33.0.0

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2+
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
3+
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
4+
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
5+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
6+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
7+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
8+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
9+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
10+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
11+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
12+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=

internal/track.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/google/go-github/v33/github"
9+
)
10+
11+
// Track tracks public GitHub repositories, continuously updating according to the given interval.
12+
//
13+
// The given interval must be greater than zero.
14+
func Track(interval time.Duration) error {
15+
for ; ; <-time.Tick(interval) {
16+
client := github.NewClient(nil)
17+
con := context.Background()
18+
listOptions := github.ListOptions{PerPage: 3}
19+
searchOptions := &github.SearchOptions{ListOptions: listOptions, Sort: "updated"}
20+
result, _, err := client.Search.Repositories(con, "is:public", searchOptions)
21+
if err != nil {
22+
return err
23+
}
24+
for _, repository := range result.Repositories {
25+
fmt.Println(*repository.Name)
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)