mirror of
https://github.com/taigrr/gico.git
synced 2026-04-02 03:09:07 -07:00
finish merging
This commit is contained in:
@@ -9,13 +9,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
wfreq := []int{}
|
n := time.Now()
|
||||||
yd := time.Now().YearDay()
|
|
||||||
repoPaths, err := commits.GetMRRepos()
|
repoPaths, err := commits.GetMRRepos()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
freq, err := repoPaths.GlobalFrequency(time.Now().Year(), []string{""})
|
freq, err := repoPaths.Frequency(n.Year(), []string{""})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
wfreq, err := repoPaths.GetWeekFreq([]string{""})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type Repo git.Repository
|
|||||||
func main() {
|
func main() {
|
||||||
year := time.Now().Year() - 1
|
year := time.Now().Year() - 1
|
||||||
authors := []string{"Groot"}
|
authors := []string{"Groot"}
|
||||||
gfreq, err := commits.GlobalFrequencyChan(year, authors)
|
gfreq, err := commits.FrequencyChan(year, authors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,33 +18,14 @@ func main() {
|
|||||||
r.HandleFunc("/weekly.svg", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/weekly.svg", func(w http.ResponseWriter, r *http.Request) {
|
||||||
author := r.URL.Query().Get("author")
|
author := r.URL.Query().Get("author")
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
now := time.Now()
|
|
||||||
year := now.Year()
|
|
||||||
repoPaths, err := commits.GetMRRepos()
|
repoPaths, err := commits.GetMRRepos()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
freq, err := repoPaths.FrequencyChan(year, []string{author})
|
week, err := repoPaths.GetWeekFreq([]string{author})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
today := now.YearDay() - 1
|
|
||||||
fmt.Println(today)
|
|
||||||
if today < 6 {
|
|
||||||
curYear := year - 1
|
|
||||||
curFreq, err := repoPaths.FrequencyChan(curYear, []string{author})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
freq = append(curFreq, freq...)
|
|
||||||
today += 365
|
|
||||||
if curYear%4 == 0 {
|
|
||||||
today++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println(freq)
|
|
||||||
|
|
||||||
week := freq[today-6 : today+1]
|
|
||||||
svg := svg.GetWeekSVG(week)
|
svg := svg.GetWeekSVG(week)
|
||||||
svg.WriteTo(w)
|
svg.WriteTo(w)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
mapTex sync.RWMutex
|
mapTex sync.RWMutex
|
||||||
hashCache map[int]map[string]map[string]types.ExpYearFreq
|
hashCache map[int]map[string]map[string]types.ExpFreq
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
hashCache = make(map[int]map[string]map[string]types.ExpYearFreq)
|
hashCache = make(map[int]map[string]map[string]types.ExpFreq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashSlice(in []string) string {
|
func hashSlice(in []string) string {
|
||||||
@@ -32,22 +32,22 @@ func hashSlice(in []string) string {
|
|||||||
return fmt.Sprintf("%x\n", b)
|
return fmt.Sprintf("%x\n", b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCachedGraph(year int, authors []string, repoPaths []string) (types.YearFreq, bool) {
|
func GetCachedGraph(year int, authors []string, repoPaths []string) (types.Freq, bool) {
|
||||||
a := hashSlice(authors)
|
a := hashSlice(authors)
|
||||||
r := hashSlice(repoPaths)
|
r := hashSlice(repoPaths)
|
||||||
mapTex.RLock()
|
mapTex.RLock()
|
||||||
defer mapTex.RUnlock()
|
defer mapTex.RUnlock()
|
||||||
if m1, ok := hashCache[year]; !ok {
|
if m1, ok := hashCache[year]; !ok {
|
||||||
return types.YearFreq{}, false
|
return types.Freq{}, false
|
||||||
} else {
|
} else {
|
||||||
if m2, ok := m1[a]; !ok {
|
if m2, ok := m1[a]; !ok {
|
||||||
return types.YearFreq{}, false
|
return types.Freq{}, false
|
||||||
} else {
|
} else {
|
||||||
if freq, ok := m2[r]; !ok {
|
if freq, ok := m2[r]; !ok {
|
||||||
return types.YearFreq{}, false
|
return types.Freq{}, false
|
||||||
} else {
|
} else {
|
||||||
if freq.Created.Before(time.Now().Add(-15 * time.Minute)) {
|
if freq.Created.Before(time.Now().Add(-15 * time.Minute)) {
|
||||||
return types.YearFreq{}, false
|
return types.Freq{}, false
|
||||||
} else {
|
} else {
|
||||||
return freq.YearFreq, true
|
return freq.YearFreq, true
|
||||||
}
|
}
|
||||||
@@ -56,18 +56,18 @@ func GetCachedGraph(year int, authors []string, repoPaths []string) (types.YearF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CacheGraph(year int, authors, repoPaths []string, freq types.YearFreq) {
|
func CacheGraph(year int, authors, repoPaths []string, freq types.Freq) {
|
||||||
a := hashSlice(authors)
|
a := hashSlice(authors)
|
||||||
r := hashSlice(repoPaths)
|
r := hashSlice(repoPaths)
|
||||||
mapTex.Lock()
|
mapTex.Lock()
|
||||||
defer mapTex.Unlock()
|
defer mapTex.Unlock()
|
||||||
if _, ok := hashCache[year]; !ok {
|
if _, ok := hashCache[year]; !ok {
|
||||||
hashCache[year] = make(map[string]map[string]types.ExpYearFreq)
|
hashCache[year] = make(map[string]map[string]types.ExpFreq)
|
||||||
}
|
}
|
||||||
if _, ok := hashCache[year][a]; !ok {
|
if _, ok := hashCache[year][a]; !ok {
|
||||||
hashCache[year][a] = make(map[string]types.ExpYearFreq)
|
hashCache[year][a] = make(map[string]types.ExpFreq)
|
||||||
}
|
}
|
||||||
hashCache[year][a][r] = types.ExpYearFreq{YearFreq: freq, Created: time.Now()}
|
hashCache[year][a][r] = types.ExpFreq{YearFreq: freq, Created: time.Now()}
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Minute * 15)
|
time.Sleep(time.Minute * 15)
|
||||||
mapTex.Lock()
|
mapTex.Lock()
|
||||||
|
|||||||
@@ -8,19 +8,13 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
|
||||||
"github.com/taigrr/gico/types"
|
"github.com/taigrr/gico/types"
|
||||||
"github.com/taigrr/mg/parse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GlobalFrequencyChan(year int, authors []string) (types.YearFreq, error) {
|
func (paths RepoSet) FrequencyChan(year int, authors []string) (types.Freq, error) {
|
||||||
yearLength := 365
|
yearLength := 365
|
||||||
if year%4 == 0 {
|
if year%4 == 0 {
|
||||||
yearLength++
|
yearLength++
|
||||||
}
|
}
|
||||||
mrconf, err := parse.LoadMRConfig()
|
|
||||||
if err != nil {
|
|
||||||
return types.YearFreq{}, err
|
|
||||||
}
|
|
||||||
paths := mrconf.GetRepoPaths()
|
|
||||||
cache, ok := GetCachedGraph(year, authors, paths)
|
cache, ok := GetCachedGraph(year, authors, paths)
|
||||||
if ok {
|
if ok {
|
||||||
return cache, nil
|
return cache, nil
|
||||||
@@ -58,6 +52,21 @@ func GlobalFrequencyChan(year int, authors []string) (types.YearFreq, error) {
|
|||||||
return freq, nil
|
return freq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func YearFreqFromChan(cc chan types.Commit, year int) types.Freq {
|
||||||
|
yearLength := 365
|
||||||
|
if year%4 == 0 {
|
||||||
|
yearLength++
|
||||||
|
}
|
||||||
|
freq := make([]int, yearLength)
|
||||||
|
for commit := range cc {
|
||||||
|
if commit.TimeStamp.Year() != year {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
freq[commit.TimeStamp.YearDay()-1]++
|
||||||
|
}
|
||||||
|
return freq
|
||||||
|
}
|
||||||
|
|
||||||
func (repo Repo) GetCommitChan() (chan types.Commit, error) {
|
func (repo Repo) GetCommitChan() (chan types.Commit, error) {
|
||||||
cc := make(chan types.Commit, 30)
|
cc := make(chan types.Commit, 30)
|
||||||
r := git.Repository(repo)
|
r := git.Repository(repo)
|
||||||
@@ -81,7 +90,7 @@ func (repo Repo) GetCommitChan() (chan types.Commit, error) {
|
|||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func YearFreqFromChan(cc chan types.Commit, year int) types.YearFreq {
|
func FreqFromChan(cc chan types.Commit, year int) types.Freq {
|
||||||
yearLength := 365
|
yearLength := 365
|
||||||
if year%4 == 0 {
|
if year%4 == 0 {
|
||||||
yearLength++
|
yearLength++
|
||||||
|
|||||||
@@ -1,99 +1,15 @@
|
|||||||
package commits
|
package commits
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
git "github.com/go-git/go-git/v5"
|
git "github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
|
||||||
"github.com/taigrr/gico/types"
|
"github.com/taigrr/gico/types"
|
||||||
"github.com/taigrr/mg/parse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repo git.Repository
|
|
||||||
|
|
||||||
type RepoSet []string
|
|
||||||
|
|
||||||
var (
|
|
||||||
mapTex sync.RWMutex
|
|
||||||
hashCache map[int]map[string]map[string]types.ExpFreq
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
hashCache = make(map[int]map[string]map[string]types.ExpFreq)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashSlice(in []string) string {
|
|
||||||
sort.Strings(in)
|
|
||||||
sb := strings.Builder{}
|
|
||||||
for _, s := range in {
|
|
||||||
sb.WriteString(s)
|
|
||||||
}
|
|
||||||
h := md5.New()
|
|
||||||
h.Write([]byte(sb.String()))
|
|
||||||
b := h.Sum(nil)
|
|
||||||
return fmt.Sprintf("%x\n", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCachedGraph(year int, authors []string, repoPaths []string) (types.Freq, bool) {
|
|
||||||
a := hashSlice(authors)
|
|
||||||
r := hashSlice(repoPaths)
|
|
||||||
mapTex.RLock()
|
|
||||||
defer mapTex.RUnlock()
|
|
||||||
if m1, ok := hashCache[year]; !ok {
|
|
||||||
return types.Freq{}, false
|
|
||||||
} else {
|
|
||||||
if m2, ok := m1[a]; !ok {
|
|
||||||
return types.Freq{}, false
|
|
||||||
} else {
|
|
||||||
if freq, ok := m2[r]; !ok {
|
|
||||||
return types.Freq{}, false
|
|
||||||
} else {
|
|
||||||
if freq.Created.Before(time.Now().Add(-15 * time.Minute)) {
|
|
||||||
return types.Freq{}, false
|
|
||||||
} else {
|
|
||||||
return freq.YearFreq, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CacheGraph(year int, authors, repoPaths []string, freq types.Freq) {
|
|
||||||
a := hashSlice(authors)
|
|
||||||
r := hashSlice(repoPaths)
|
|
||||||
mapTex.Lock()
|
|
||||||
defer mapTex.Unlock()
|
|
||||||
if _, ok := hashCache[year]; !ok {
|
|
||||||
hashCache[year] = make(map[string]map[string]types.ExpFreq)
|
|
||||||
}
|
|
||||||
if _, ok := hashCache[year][a]; !ok {
|
|
||||||
hashCache[year][a] = make(map[string]types.ExpFreq)
|
|
||||||
}
|
|
||||||
hashCache[year][a][r] = types.ExpFreq{YearFreq: freq, Created: time.Now()}
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Minute * 15)
|
|
||||||
mapTex.Lock()
|
|
||||||
defer mapTex.Unlock()
|
|
||||||
delete(hashCache[year][a], r)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMRRepos() (RepoSet, error) {
|
|
||||||
mrconf, err := parse.LoadMRConfig()
|
|
||||||
if err != nil {
|
|
||||||
return RepoSet{}, err
|
|
||||||
}
|
|
||||||
paths := mrconf.GetRepoPaths()
|
|
||||||
return RepoSet(paths), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (paths RepoSet) GetWeekFreq(authors []string) (types.Freq, error) {
|
func (paths RepoSet) GetWeekFreq(authors []string) (types.Freq, error) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
year := now.Year()
|
year := now.Year()
|
||||||
@@ -119,64 +35,7 @@ func (paths RepoSet) GetWeekFreq(authors []string) (types.Freq, error) {
|
|||||||
return week, nil
|
return week, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (paths RepoSet) FrequencyChan(year int, authors []string) (types.Freq, error) {
|
func (paths RepoSet) Frequency(year int, authors []string) (types.Freq, error) {
|
||||||
yearLength := 365
|
|
||||||
if year%4 == 0 {
|
|
||||||
yearLength++
|
|
||||||
}
|
|
||||||
cache, ok := GetCachedGraph(year, authors, paths)
|
|
||||||
if ok {
|
|
||||||
return cache, nil
|
|
||||||
}
|
|
||||||
outChan := make(chan types.Commit, 10)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, p := range paths {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(path string) {
|
|
||||||
repo, err := OpenRepo(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cc, err := repo.GetCommitChan()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cc = FilterCChanByYear(cc, year)
|
|
||||||
cc, err = FilterCChanByAuthor(cc, authors)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for c := range cc {
|
|
||||||
outChan <- c
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}(p)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(outChan)
|
|
||||||
}()
|
|
||||||
freq := YearFreqFromChan(outChan, year)
|
|
||||||
CacheGraph(year, authors, paths, freq)
|
|
||||||
return freq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func YearFreqFromChan(cc chan types.Commit, year int) types.Freq {
|
|
||||||
yearLength := 365
|
|
||||||
if year%4 == 0 {
|
|
||||||
yearLength++
|
|
||||||
}
|
|
||||||
freq := make([]int, yearLength)
|
|
||||||
for commit := range cc {
|
|
||||||
if commit.TimeStamp.Year() != year {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
freq[commit.TimeStamp.YearDay()-1]++
|
|
||||||
}
|
|
||||||
return freq
|
|
||||||
}
|
|
||||||
|
|
||||||
func (paths RepoSet) GlobalFrequency(year int, authors []string) (types.Freq, error) {
|
|
||||||
yearLength := 365
|
yearLength := 365
|
||||||
if year%4 == 0 {
|
if year%4 == 0 {
|
||||||
yearLength++
|
yearLength++
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type (
|
|||||||
Commits []types.Commit
|
Commits []types.Commit
|
||||||
Year int
|
Year int
|
||||||
}
|
}
|
||||||
|
RepoSet []string
|
||||||
)
|
)
|
||||||
|
|
||||||
func OpenRepo(directory string) (Repo, error) {
|
func OpenRepo(directory string) (Repo, error) {
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ func GetYearSVG(frequencies []int) bytes.Buffer {
|
|||||||
for _, f := range frequencies {
|
for _, f := range frequencies {
|
||||||
squareColors = append(squareColors, common.ColorForFrequency(f, min, max))
|
squareColors = append(squareColors, common.ColorForFrequency(f, min, max))
|
||||||
}
|
}
|
||||||
return drawYearImage(squareColors)
|
return drawYearImage(squareColors, frequencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawYearImage(c []sc.SimpleColor) bytes.Buffer {
|
func drawYearImage(c []sc.SimpleColor, freq []int) bytes.Buffer {
|
||||||
var sb bytes.Buffer
|
var sb bytes.Buffer
|
||||||
sbw := bufio.NewWriter(&sb)
|
sbw := bufio.NewWriter(&sb)
|
||||||
squareLength := 10
|
squareLength := 10
|
||||||
@@ -66,7 +66,7 @@ func drawYearImage(c []sc.SimpleColor) bytes.Buffer {
|
|||||||
canvas := svg.New(sbw)
|
canvas := svg.New(sbw)
|
||||||
canvas.Start(width, height)
|
canvas.Start(width, height)
|
||||||
for i, s := range c {
|
for i, s := range c {
|
||||||
canvas.Square(2*squareLength+width/(len(c)/7+1)*(i/7)+squareLength*2, squareLength/2+height/7*(i%7), squareLength, fmt.Sprintf("fill:%s", s.ToHex()))
|
canvas.Square(2*squareLength+width/(len(c)/7+1)*(i/7)+squareLength*2, squareLength/2+height/7*(i%7), squareLength, fmt.Sprintf("fill:%s; value:%d", s.ToHex(), freq[i]))
|
||||||
}
|
}
|
||||||
// canvas.Text(2*squareLength, squareLength*3, "Mon", fmt.Sprintf("text-anchor:middle;font-size:%dpx;fill:black", squareLength))
|
// canvas.Text(2*squareLength, squareLength*3, "Mon", fmt.Sprintf("text-anchor:middle;font-size:%dpx;fill:black", squareLength))
|
||||||
// canvas.Text(2*squareLength, int(float64(squareLength)*6.5), "Wed", fmt.Sprintf("text-anchor:middle;font-size:%dpx;fill:black", squareLength))
|
// canvas.Text(2*squareLength, int(float64(squareLength)*6.5), "Wed", fmt.Sprintf("text-anchor:middle;font-size:%dpx;fill:black", squareLength))
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ func drawYearUnicode(c []sc.SimpleColor) string {
|
|||||||
// o := termenv.NewOutput(os.Stdout)
|
// o := termenv.NewOutput(os.Stdout)
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
o := termenv.NewOutputWithProfile(os.Stdout, termenv.TrueColor)
|
o := termenv.NewOutputWithProfile(os.Stdout, termenv.TrueColor)
|
||||||
weeks := [7][]sc.SimpleColor{{}}
|
weekRows := [7][]sc.SimpleColor{{}}
|
||||||
for i := 0; i < 7; i++ {
|
for i := 0; i < 7; i++ {
|
||||||
weeks[i] = []sc.SimpleColor{}
|
weekRows[i] = []sc.SimpleColor{}
|
||||||
}
|
}
|
||||||
for i := range c {
|
for i := 0; i < len(c); i++ {
|
||||||
weeks[i%7] = append(weeks[i%7], c[i])
|
weekRows[i%7] = append(weekRows[i%7], c[i])
|
||||||
}
|
}
|
||||||
for _, row := range weeks {
|
for _, row := range weekRows {
|
||||||
for w, d := range row {
|
for w, d := range row {
|
||||||
style := o.String(block).Foreground(termenv.TrueColor.Color(d.ToHex()))
|
style := o.String(block).Foreground(termenv.TrueColor.Color(d.ToHex()))
|
||||||
s.WriteString(style.String())
|
s.WriteString(style.String())
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ func NewCommit(Author, Message, Repo, Path string, LOC int) Commit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (yf YearFreq) String() string {
|
func (yf Freq) String() string {
|
||||||
return gterm.GetYearUnicode(yf)
|
return gterm.GetYearUnicode(yf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a YearFreq) Merge(b YearFreq) YearFreq {
|
func (a Freq) Merge(b Freq) Freq {
|
||||||
x := len(a)
|
x := len(a)
|
||||||
y := len(b)
|
y := len(b)
|
||||||
if x < y {
|
if x < y {
|
||||||
x = y
|
x = y
|
||||||
}
|
}
|
||||||
c := make(YearFreq, x)
|
c := make(Freq, x)
|
||||||
copy(c, a)
|
copy(c, a)
|
||||||
for i := 0; i < y; i++ {
|
for i := 0; i < y; i++ {
|
||||||
c[i] += b[i]
|
c[i] += b[i]
|
||||||
|
|||||||
Reference in New Issue
Block a user