Skip to content

Commit a96b39d

Browse files
committed
Add repo chart
1 parent 3972625 commit a96b39d

File tree

14 files changed

+830
-43
lines changed

14 files changed

+830
-43
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
2-
*.DS_Store
2+
*.DS_Store
3+
*/test

cmd/render.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ import (
2626
"bytes"
2727
"encoding/json"
2828
"fmt"
29+
"strings"
2930

3031
"github.com/anqiansong/github-compare/pkg/stat"
32+
ui "github.com/gizak/termui/v3"
33+
"github.com/gizak/termui/v3/widgets"
3134
"github.com/jedib0t/go-pretty/v6/table"
3235
"github.com/spf13/viper"
3336
"gopkg.in/yaml.v3"
@@ -48,6 +51,10 @@ func render(printStyle style, list ...stat.Data) error {
4851
return err
4952
}
5053

54+
if len(data) == 1 {
55+
return renderDetail(data[0])
56+
}
57+
5158
t := createTable(data, true)
5259
t.SetStyle(table.StyleLight)
5360
prettyText = t.Render()
@@ -139,3 +146,153 @@ func createRow(title string, field string, emoji bool, data ...*viper.Viper) tab
139146
}
140147
return ret
141148
}
149+
150+
const barWidth = 3
151+
152+
func renderDetail(data *viper.Viper) error {
153+
if err := ui.Init(); err != nil {
154+
return err
155+
}
156+
defer ui.Close()
157+
158+
starBar := createStarBarChart(data.Get("latestMonthStargazers").(stat.Chart),
159+
"Star(Latest Week)", ui.ColorRed, func() []ui.Color {
160+
var colorList []ui.Color
161+
for i := 1; i < 18; i++ {
162+
colorList = append(colorList, ui.Color(i))
163+
}
164+
return colorList
165+
}()...)
166+
167+
forkBar := createStarBarChart(data.Get("latestWeekForks").(stat.Chart),
168+
"Forks(Latest Week)", ui.ColorGreen)
169+
commitBar := createStarBarChart(data.Get("latestWeekCommits").(stat.Chart),
170+
"Commits(Latest Week)", ui.ColorYellow)
171+
pullBar := createStarBarChart(data.Get("latestWeekPulls").(stat.Chart),
172+
"Pulls(Latest Week)", ui.ColorWhite)
173+
issueBar := createStarBarChart(data.Get("latestWeekIssues").(stat.Chart),
174+
"Issues(Latest Week)", ui.ColorCyan)
175+
176+
desc := creatParagraph("About", ui.ColorYellow, func() []string {
177+
return []string{
178+
fmt.Sprintf("[◉ Homepage: %s](fg:green)", data.GetString("homepage")),
179+
fmt.Sprintf("[◉ Description: %s](fg:white)", data.GetString("description")),
180+
fmt.Sprintf("◉ Tags: %s", formatTags(data.GetStringSlice("tags"))),
181+
}
182+
}()...)
183+
desc.TextStyle = ui.NewStyle(ui.ColorGreen)
184+
185+
metrics1 := creatParagraph("Metrics1", ui.ColorRed, func() []string {
186+
return []string{
187+
fmt.Sprintf("[◉ TotalStars: %s](fg:red)", data.GetString("starCount")),
188+
fmt.Sprintf("[◉ TotalForks: %s](fg:green)", data.GetString("forkCount")),
189+
fmt.Sprintf("[◉ TotalWatcers: %s](fg:yellow)", data.GetString("watcherCount")),
190+
fmt.Sprintf("[◉ TotalContributors: %s](fg:cyan)", data.GetString("contributorCount")),
191+
}
192+
}()...)
193+
194+
metrics2 := creatParagraph("Metrics2", ui.ColorGreen, func() []string {
195+
return []string{
196+
fmt.Sprintf("[◉ LatestDayStars: %s](fg:red)", data.GetString("latestDayStarCount")),
197+
fmt.Sprintf("[◉ LatestWeekStars: %s](fg:green)", data.GetString("latestWeekStarCount")),
198+
fmt.Sprintf("[◉ LatestMonthStars: %s](fg:yellow)",
199+
data.GetString("latestMonthStarCount")),
200+
fmt.Sprintf("[◉ ReleaseCount: %s](fg:cyan)", data.GetString("releaseCount")),
201+
}
202+
}()...)
203+
204+
metrics3 := creatParagraph("Metrics3", ui.ColorYellow, func() []string {
205+
return []string{
206+
fmt.Sprintf("[◉ Issue: %s](fg:green)", data.GetString("issue")),
207+
fmt.Sprintf("[◉ Pull: %s](fg:white)", data.GetString("pull")),
208+
fmt.Sprintf("[◉ License: %s](fg:yellow)", data.GetString("license")),
209+
fmt.Sprintf("[◉ Language: %s](fg:cyan)", data.GetString("language")),
210+
}
211+
}()...)
212+
213+
metrics4 := creatParagraph("Metrics4", ui.ColorCyan, func() []string {
214+
return []string{
215+
fmt.Sprintf("[◉ Age: %s](fg:green)", data.GetString("age")),
216+
fmt.Sprintf("[◉ LatestReleaseAt: %s](fg:white)", data.GetString("latestReleaseAt")),
217+
fmt.Sprintf("[◉ LastUpdatedAt: %s](fg:yellow)", data.GetString("lastUpdatedAt")),
218+
fmt.Sprintf("[◉ LastUpdatedAt: %s](fg:cyan)", data.GetString("lastUpdatedAt")),
219+
}
220+
}()...)
221+
222+
grid := ui.NewGrid()
223+
termWidth, termHeight := ui.TerminalDimensions()
224+
grid.SetRect(0, 0, termWidth, termHeight)
225+
grid.Set(
226+
ui.NewRow(1.0/4, ui.NewCol(1.0, starBar)),
227+
ui.NewRow(1.0/4,
228+
ui.NewCol(1.0/4, forkBar),
229+
ui.NewCol(1.0/4, commitBar),
230+
ui.NewCol(1.0/4, pullBar),
231+
ui.NewCol(1.0/4, issueBar),
232+
),
233+
ui.NewRow(1.0/4, ui.NewCol(1.0, desc)),
234+
ui.NewRow(1.0/4,
235+
ui.NewCol(1.0/4, metrics1),
236+
ui.NewCol(1.0/4, metrics2),
237+
ui.NewCol(1.0/4, metrics3),
238+
ui.NewCol(1.0/4, metrics4),
239+
),
240+
)
241+
ui.Render(grid)
242+
243+
uiEvents := ui.PollEvents()
244+
for {
245+
e := <-uiEvents
246+
switch e.ID {
247+
case "q", "<C-c>":
248+
ui.Clear()
249+
return nil
250+
case "<Resize>":
251+
payload := e.Payload.(ui.Resize)
252+
grid.SetRect(0, 0, payload.Width, payload.Height)
253+
ui.Clear()
254+
ui.Render(grid)
255+
}
256+
}
257+
}
258+
259+
var colorString = []string{"black", "red", "green", "yellow", "blue", "magenta", "cyan"}
260+
261+
func formatTags(tags []string) string {
262+
var ret []string
263+
for idx, e := range tags {
264+
colorIndex := idx % len(colorString)
265+
ret = append(ret, fmt.Sprintf("[%s](fg:white,bg: %s)", e, colorString[colorIndex]))
266+
}
267+
return strings.Join(ret, " ")
268+
}
269+
270+
func creatParagraph(title string, titleColor ui.Color, list ...string) *widgets.Paragraph {
271+
p := widgets.NewParagraph()
272+
p.Text = strings.Join(list, "\n")
273+
p.Title = title
274+
p.TextStyle = ui.NewStyle(titleColor)
275+
return p
276+
}
277+
278+
func createStarBarChart(data stat.Chart, title string, titleColor ui.Color,
279+
barColors ...ui.Color) *widgets.BarChart {
280+
bar := widgets.NewBarChart()
281+
bar.Title = title
282+
bar.Data = data.Data
283+
bar.BarWidth = barWidth
284+
bar.Labels = data.Labels
285+
bar.TitleStyle = ui.NewStyle(titleColor)
286+
if len(barColors) > 0 {
287+
bar.BarColors = barColors
288+
bar.LabelStyles = func() []ui.Style {
289+
var colorList []ui.Style
290+
for _, c := range barColors {
291+
colorList = append(colorList, ui.NewStyle(c))
292+
}
293+
return colorList
294+
}()
295+
}
296+
bar.NumStyles = []ui.Style{ui.NewStyle(ui.ColorBlack)}
297+
return bar
298+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/briandowns/spinner v1.18.1
77
github.com/charmbracelet/lipgloss v0.5.0
88
github.com/fatih/color v1.13.0
9+
github.com/gizak/termui/v3 v3.1.0
910
github.com/google/go-github/v44 v44.1.1-0.20220516192235-2d872b40760d
1011
github.com/jedib0t/go-pretty/v6 v6.3.1
1112
github.com/kevwan/mapreduce/v2 v2.1.1
@@ -27,9 +28,11 @@ require (
2728
github.com/mattn/go-colorable v0.1.12 // indirect
2829
github.com/mattn/go-isatty v0.0.14 // indirect
2930
github.com/mattn/go-runewidth v0.0.13 // indirect
31+
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
3032
github.com/mitchellh/mapstructure v1.4.3 // indirect
3133
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect
3234
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
35+
github.com/nsf/termbox-go v1.1.1 // indirect
3336
github.com/pelletier/go-toml v1.9.4 // indirect
3437
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
3538
github.com/rivo/uniseg v0.2.0 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
6565
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
6666
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
6767
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
68+
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
69+
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
6870
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
6971
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
7072
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -164,15 +166,23 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
164166
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
165167
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
166168
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
169+
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
170+
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
167171
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
168172
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
169173
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
174+
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
175+
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
176+
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
170177
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
171178
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
172179
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
173180
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
174181
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
175182
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
183+
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
184+
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
185+
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
176186
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
177187
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
178188
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0=

pkg/stat/commit.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2022 anqiansong
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
package stat
24+
25+
import (
26+
"time"
27+
28+
"github.com/anqiansong/github-compare/pkg/timex"
29+
"github.com/shurcooL/githubv4"
30+
)
31+
32+
type (
33+
CommitList []CommitCommentEdge
34+
35+
CommitComment struct {
36+
CreatedAt githubv4.DateTime
37+
}
38+
39+
CommitCommentEdge struct {
40+
Cursor githubv4.String
41+
Node ForkRepository
42+
}
43+
44+
CommitCommentConnection struct {
45+
Edges []CommitCommentEdge
46+
PageInfo PageInfo
47+
TotalCount githubv4.Int
48+
}
49+
50+
Commit struct {
51+
List CommitCommentConnection `graphql:"commitComments(first: 100, after: $after)"`
52+
}
53+
54+
CommitQuery struct {
55+
Commits Commit `graphql:"repository(owner: $owner, name: $name)"`
56+
}
57+
)
58+
59+
func (c CommitList) Chart() Chart {
60+
now := time.Now()
61+
var (
62+
dayCount = make(map[string]int)
63+
labels []string
64+
data []float64
65+
dayTime = timex.AllDays(now.Add(-7*24*time.Hour), now)
66+
)
67+
68+
for _, t := range dayTime {
69+
label := t.Format("02/01")
70+
labels = append(labels, label)
71+
dayCount[label] += c.getSpecifiedDate(t)
72+
}
73+
74+
return Chart{Data: data, Labels: labels}
75+
}
76+
77+
func (c CommitList) getSpecifiedDate(date time.Time) int {
78+
zero := timex.Truncate(date)
79+
var count int
80+
for _, e := range c {
81+
if timex.Truncate(e.Node.CreatedAt.Time).Equal(zero) {
82+
count += 1
83+
}
84+
}
85+
return count
86+
}
87+
88+
func (s Stat) latestWeekCommits() CommitList {
89+
var (
90+
list CommitList
91+
brk bool
92+
after githubv4.String
93+
commitQuery CommitQuery
94+
)
95+
96+
deadline := time.Now().Add(-7 * 24 * time.Hour)
97+
arg := map[string]interface{}{
98+
"after": (*githubv4.String)(nil),
99+
"owner": githubv4.String(s.owner),
100+
"name": githubv4.String(s.repo),
101+
}
102+
103+
for {
104+
_ = s.graphqlClient.Query(s.ctx, &commitQuery, arg)
105+
temp := commitQuery.Commits.List.Edges
106+
107+
for _, e := range temp {
108+
if e.Node.CreatedAt.Time.Before(deadline) {
109+
brk = true
110+
break
111+
}
112+
list = append(list, e)
113+
}
114+
if brk || !(bool)(commitQuery.Commits.List.PageInfo.HasNextPage) || len(temp) == 0 {
115+
break
116+
}
117+
118+
after = temp[len(temp)-1].Cursor
119+
arg["after"] = after
120+
}
121+
122+
return list
123+
}

0 commit comments

Comments
 (0)