Skip to content

Commit 0359be3

Browse files
committed
initial commit
0 parents  commit 0359be3

File tree

5 files changed

+311
-0
lines changed

5 files changed

+311
-0
lines changed

.github/workflows/release.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: release
2+
on:
3+
push:
4+
tags:
5+
- "v*"
6+
permissions:
7+
contents: write
8+
9+
jobs:
10+
release:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- uses: cli/gh-extension-precompile@v1

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/gh-langs
2+
/gh-langs.exe

go.mod

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module github.com/yanskun/gh-langs
2+
3+
go 1.19
4+
5+
require github.com/cli/go-gh/v2 v2.9.0
6+
7+
require (
8+
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
9+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
10+
github.com/cli/safeexec v1.0.0 // indirect
11+
github.com/cli/shurcooL-graphql v0.0.4 // indirect
12+
github.com/go-openapi/errors v0.22.0 // indirect
13+
github.com/go-openapi/strfmt v0.23.0 // indirect
14+
github.com/google/go-github/v61 v61.0.1-0.20240501183028-faa0e7ceab73 // indirect
15+
github.com/google/go-querystring v1.1.0 // indirect
16+
github.com/google/uuid v1.6.0 // indirect
17+
github.com/henvic/httpretty v0.0.6 // indirect
18+
github.com/jedib0t/go-pretty v4.3.0+incompatible // indirect
19+
github.com/kr/text v0.2.0 // indirect
20+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
21+
github.com/mattn/go-isatty v0.0.20 // indirect
22+
github.com/mattn/go-runewidth v0.0.15 // indirect
23+
github.com/mitchellh/mapstructure v1.5.0 // indirect
24+
github.com/muesli/termenv v0.15.2 // indirect
25+
github.com/oklog/ulid v1.3.1 // indirect
26+
github.com/rivo/uniseg v0.4.7 // indirect
27+
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect
28+
go.mongodb.org/mongo-driver v1.14.0 // indirect
29+
golang.org/x/sys v0.19.0 // indirect
30+
golang.org/x/term v0.13.0 // indirect
31+
golang.org/x/text v0.14.0 // indirect
32+
gopkg.in/yaml.v3 v3.0.1 // indirect
33+
)

go.sum

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
2+
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
3+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
4+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
5+
github.com/cli/go-gh/v2 v2.9.0 h1:D3lTjEneMYl54M+WjZ+kRPrR5CEJ5BHS05isBPOV3LI=
6+
github.com/cli/go-gh/v2 v2.9.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE=
7+
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
8+
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
9+
github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY=
10+
github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk=
11+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
12+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
13+
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
14+
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
15+
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
16+
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
17+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
18+
github.com/google/go-github/v61 v61.0.1-0.20240501183028-faa0e7ceab73 h1:smFL3TWVxdNIdDs4tLA5m0331XTm0v8Y7CXZQefxP2I=
19+
github.com/google/go-github/v61 v61.0.1-0.20240501183028-faa0e7ceab73/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
20+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
21+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
22+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
23+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
24+
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
25+
github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs=
26+
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
27+
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
28+
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
29+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
30+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
31+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
32+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
33+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
34+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
35+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
36+
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
37+
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
38+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
39+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
40+
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
41+
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
42+
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
43+
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
44+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
45+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
46+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
47+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
48+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
49+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
50+
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8=
51+
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
52+
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
53+
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
54+
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
55+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
56+
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
57+
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
58+
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
59+
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
60+
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
61+
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
62+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
63+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
64+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
65+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
66+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
67+
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
68+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
69+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

main.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"log"
9+
"net/http"
10+
"os"
11+
"os/exec"
12+
"sort"
13+
"strings"
14+
"sync"
15+
16+
"github.com/cli/go-gh/v2/pkg/api"
17+
"github.com/google/go-github/v61/github"
18+
"github.com/jedib0t/go-pretty/table"
19+
"golang.org/x/text/message"
20+
)
21+
22+
func main() {
23+
org := flag.String("org", "", "organization name")
24+
user := flag.String("user", "", "user name")
25+
flag.StringVar(org, "o", "", "organization name (shorthand)")
26+
flag.StringVar(user, "u", "", "user name (shorthand)")
27+
flag.Parse()
28+
29+
var account string
30+
if *org != "" {
31+
account = fmt.Sprintf("orgs/%s", *org)
32+
} else if *user != "" {
33+
account = fmt.Sprintf("users/%s", *user)
34+
} else {
35+
username, err := getGitHubUsername()
36+
if err != nil {
37+
log.Fatal(err)
38+
return
39+
}
40+
account = fmt.Sprintf("users/%s", username)
41+
}
42+
43+
client, err := api.DefaultRESTClient()
44+
if err != nil {
45+
log.Fatal(err)
46+
return
47+
}
48+
49+
repos, err := getRepositories(client, account)
50+
if err != nil {
51+
log.Fatal(err)
52+
return
53+
}
54+
55+
results, err := getLanguages(client, repos)
56+
if err != nil {
57+
log.Fatal(err)
58+
return
59+
}
60+
61+
languages := sumLanguages(results)
62+
63+
printTable(languages)
64+
fmt.Println(fmt.Sprintf("https:github.com/%s has %d repositories", account, len(repos)))
65+
}
66+
67+
func getGitHubUsername() (string, error) {
68+
cmd := exec.Command("gh", "api", "user", "--jq", ".login")
69+
var out bytes.Buffer
70+
cmd.Stdout = &out
71+
err := cmd.Run()
72+
if err != nil {
73+
return "", err
74+
}
75+
return strings.TrimSpace(out.String()), nil
76+
}
77+
78+
func getRepositories(client *api.RESTClient, account string) ([]github.Repository, error) {
79+
var repos []github.Repository
80+
page := 1
81+
82+
for {
83+
endpoint := fmt.Sprintf("%s/repos?sort=updated&per_page=100&page=%d", account, page)
84+
response, err := client.Request(http.MethodGet, endpoint, nil)
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
decoder := json.NewDecoder(response.Body)
90+
data := []github.Repository{}
91+
if err := decoder.Decode(&data); err != nil {
92+
return nil, err
93+
}
94+
if err := response.Body.Close(); err != nil {
95+
return nil, err
96+
}
97+
98+
if len(data) == 0 {
99+
break
100+
}
101+
102+
repos = append(repos, data...)
103+
page++
104+
}
105+
return repos, nil
106+
}
107+
108+
type (
109+
Languages map[string]int
110+
LanguagesList []Languages
111+
)
112+
113+
func getLanguages(client *api.RESTClient, data []github.Repository) (LanguagesList, error) {
114+
results := make(LanguagesList, 0, len(data))
115+
116+
var wg sync.WaitGroup
117+
118+
for _, repo := range data {
119+
wg.Add(1)
120+
go func(repo github.Repository) {
121+
defer wg.Done()
122+
123+
fullName := repo.GetFullName()
124+
response, err := client.Request(http.MethodGet, fmt.Sprintf("repos/%s/languages", fullName), nil)
125+
if err != nil {
126+
log.Fatal(err)
127+
return
128+
}
129+
130+
decoder := json.NewDecoder(response.Body)
131+
data := Languages{}
132+
if err := decoder.Decode(&data); err != nil {
133+
log.Fatal(err)
134+
return
135+
}
136+
137+
if err := response.Body.Close(); err != nil {
138+
log.Fatal(err)
139+
return
140+
}
141+
142+
results = append(results, data)
143+
}(repo)
144+
}
145+
wg.Wait()
146+
147+
return results, nil
148+
}
149+
150+
func sumLanguages(list LanguagesList) Languages {
151+
results := Languages{}
152+
153+
for _, languages := range list {
154+
for lang, lines := range languages {
155+
results[lang] += lines
156+
}
157+
}
158+
159+
return results
160+
}
161+
162+
type Pair struct {
163+
Key string
164+
Value int
165+
}
166+
167+
func printTable(languages Languages) {
168+
t := table.NewWriter()
169+
t.SetOutputMirror(os.Stdout)
170+
p := message.NewPrinter(message.MatchLanguage("en"))
171+
t.AppendHeader(table.Row{"Language", "Lines"})
172+
173+
// Convert map to slice
174+
pairs := make([]Pair, 0, len(languages))
175+
for k, v := range languages {
176+
pairs = append(pairs, Pair{k, v})
177+
}
178+
179+
// Sort slice in descending order by Value
180+
sort.Slice(pairs, func(i, j int) bool {
181+
return pairs[i].Value > pairs[j].Value
182+
})
183+
184+
// Append rows in sorted order
185+
for _, pair := range pairs {
186+
num := p.Sprintf("%d", pair.Value)
187+
t.AppendRow(table.Row{pair.Key, num})
188+
}
189+
t.Render()
190+
}
191+
192+
// For more examples of using go-gh, see:
193+
// https://github.com/cli/go-gh/blob/trunk/example_gh_test.go

0 commit comments

Comments
 (0)