Skip to content

Commit d8c1e05

Browse files
committed
feat: sync to notion
1 parent 7bcb3a2 commit d8c1e05

File tree

10 files changed

+421
-144
lines changed

10 files changed

+421
-144
lines changed

cmd/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"fmt"
55
"github.com/ppsteven/leetcode-tool/cmd/gpt"
6+
"github.com/ppsteven/leetcode-tool/cmd/sync"
67
"github.com/ppsteven/leetcode-tool/internal/config"
78
"log"
89
"os"
@@ -39,6 +40,9 @@ var (
3940

4041
gptCmd = app.Command("gpt", "Use gpt to solve problem.")
4142
gptNumber = gptCmd.Arg("number", "problem number").Required().String()
43+
44+
syncCmd = app.Command("sync", "Sync leetcode records.")
45+
notion = syncCmd.Flag("notion", "sync to notion.").Bool()
4246
)
4347

4448
func showMeta(lc *leetcode.Leetcode, number string) {
@@ -73,6 +77,9 @@ func main() {
7377
case gptCmd.FullCommand():
7478
lc := leetcode.NewLeetcode(config.NewConfig())
7579
gpt.Run(lc, *gptNumber)
80+
case syncCmd.FullCommand():
81+
lc := leetcode.NewLeetcode(config.NewConfig())
82+
sync.Run(lc, *notion)
7683
}
7784
}
7885

cmd/sync/main.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package sync
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/ppsteven/leetcode-tool/internal/meta"
7+
"github.com/ppsteven/leetcode-tool/internal/notion"
8+
"github.com/ppsteven/leetcode-tool/pkg/leetcode"
9+
"golang.org/x/sync/errgroup"
10+
"log"
11+
)
12+
13+
func Run(lc *leetcode.Leetcode, isNotion bool) {
14+
tagMetas := meta.GetTagMetas()
15+
metas, ok := tagMetas["all"]
16+
if !ok {
17+
return
18+
}
19+
20+
if isNotion {
21+
nc := notion.NewNotion(lc.Config.Notion.Token).
22+
WithConfig("", lc.Config.Notion.DatabaseID)
23+
24+
g, _ := errgroup.WithContext(context.TODO())
25+
nThread := 5
26+
27+
in := make(chan *notion.Record, 10)
28+
go func() {
29+
for _, m := range metas {
30+
record := MetaToRecord(m)
31+
in <- record
32+
}
33+
close(in)
34+
}()
35+
36+
progress := make(chan struct{}, 0)
37+
for i := 0; i < nThread; i++ {
38+
g.Go(func() error {
39+
for record := range in {
40+
err := nc.Insert(record)
41+
if err != nil {
42+
return err
43+
}
44+
progress <- struct{}{}
45+
}
46+
return nil
47+
})
48+
}
49+
50+
go func() {
51+
cur := 0
52+
for range progress {
53+
cur++
54+
fmt.Printf("\rsync leetcode record to notion, progress: %d/%d", cur, len(metas))
55+
}
56+
fmt.Printf("\rsync leetcode record to notion, progress: %d/%d done.\n", cur, len(metas))
57+
}()
58+
59+
if err := g.Wait(); err != nil {
60+
log.Fatal(err)
61+
}
62+
close(progress)
63+
}
64+
}
65+
66+
func MetaToRecord(e *meta.Meta) *notion.Record {
67+
var solved string
68+
if e.Solved {
69+
solved = "Yes"
70+
} else {
71+
solved = "No"
72+
}
73+
74+
fields := []*notion.Field{
75+
{Type: "title", Name: "ID", Content: e.Index},
76+
{Type: "text", Name: "Name", Content: e.Title},
77+
{Type: "url", Name: "Link", Content: e.Link},
78+
{Type: "select", Name: "Difficulty", Content: e.Difficulty},
79+
{Type: "multi_select", Name: "Tags", Content: e.Tags},
80+
{Type: "select", Name: "Solved", Content: solved},
81+
}
82+
return &notion.Record{Fields: fields}
83+
}

cmd/update/main.go

Lines changed: 11 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,17 @@ package update
33
import (
44
"bytes"
55
"fmt"
6+
"github.com/ppsteven/leetcode-tool/internal/helper"
7+
"github.com/ppsteven/leetcode-tool/internal/meta"
68
"github.com/ppsteven/leetcode-tool/pkg/leetcode"
79
"io/ioutil"
810
"log"
911
"os"
1012
"path/filepath"
11-
"regexp"
1213
"sort"
13-
"strconv"
1414
"strings"
1515
"sync"
1616
"text/template"
17-
18-
"github.com/bmatcuk/doublestar/v2"
19-
)
20-
21-
var (
22-
indexRegex = regexp.MustCompile("@index (.+)")
23-
titleRegex = regexp.MustCompile("@title (.+)")
24-
difficultyRegex = regexp.MustCompile("@difficulty (.+)")
25-
tagsRegex = regexp.MustCompile("@tags (.+)")
26-
draftRegex = regexp.MustCompile("@draft (.+)")
27-
linkRegex = regexp.MustCompile("@link (.+)")
28-
frontendIdRegex = regexp.MustCompile("@frontendId (.+)")
29-
solvedRegex = regexp.MustCompile("@solved (.+)")
3017
)
3118

3219
const (
@@ -38,155 +25,39 @@ var (
3825
tagTpl = template.Must(template.New("tag").Parse(tagStr))
3926
)
4027

41-
type Meta struct {
42-
Index string
43-
Title string
44-
Difficulty string
45-
Tags []string
46-
Draft bool
47-
Fp string
48-
Link string
49-
FrontendId string
50-
Ext string
51-
Completed string
52-
}
53-
54-
type Metas []*Meta
55-
56-
func (a Metas) Len() int { return len(a) }
57-
func (a Metas) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
58-
func (a Metas) Less(i, j int) bool {
59-
iIndex, _ := strconv.Atoi(a[i].Index)
60-
jIndex, _ := strconv.Atoi(a[j].Index)
61-
return iIndex < jIndex
62-
}
28+
type (
29+
Meta = meta.Meta
30+
Metas = meta.Metas
31+
)
6332

6433
type TableData struct {
6534
Metas Metas
6635
Total int
6736
}
6837

69-
type TagMetas map[string](Metas)
70-
71-
func addMeta(tagMetas TagMetas, meta *Meta) {
72-
if meta == nil {
73-
return
74-
}
75-
for _, tag := range meta.Tags {
76-
if _, ok := tagMetas[tag]; !ok {
77-
tagMetas[tag] = make(Metas, 0)
78-
}
79-
tagMetas[tag] = append(tagMetas[tag], meta)
80-
}
81-
tagMetas["all"] = append(tagMetas["all"], meta)
82-
}
83-
84-
func findTag(content []byte, r *regexp.Regexp) string {
85-
res := r.FindSubmatch(content)
86-
if len(res) < 2 {
87-
return ""
88-
}
89-
return string(res[1])
90-
}
91-
92-
func findMeta(content []byte, fp string) *Meta {
93-
draft := findTag(content, draftRegex) == "" || findTag(content, draftRegex) == "true"
94-
if draft {
95-
return nil
96-
}
97-
tags := strings.Split(findTag(content, tagsRegex), ",")
98-
solved := false
99-
if strings.ToLower(findTag(content, solvedRegex)) == "true" {
100-
solved = true
101-
}
102-
103-
return &Meta{
104-
Index: findTag(content, indexRegex),
105-
Title: findTag(content, titleRegex),
106-
Difficulty: findTag(content, difficultyRegex),
107-
Tags: tags,
108-
Draft: draft,
109-
Fp: filepath.Dir(fp),
110-
Link: findTag(content, linkRegex),
111-
FrontendId: findTag(content, frontendIdRegex),
112-
Ext: filepath.Ext(fp),
113-
Completed: genCompleted(solved, filepath.Ext(fp)),
114-
}
115-
}
116-
117-
func genCompleted(isCompleted bool, ext string) string {
118-
if isCompleted {
119-
return ext[1:] + " ✅"
120-
}
121-
return ext[1:] + " ❎"
122-
}
123-
12438
func genTable(data *TableData) string {
12539
var bf bytes.Buffer
12640
sort.Sort(data.Metas)
12741
tableTpl.Execute(&bf, data)
12842
return bf.String()
12943
}
13044

131-
func fileExists(path string) bool {
132-
_, err := os.Stat(path)
133-
return !os.IsNotExist(err)
134-
}
135-
136-
func isDirectory(path string) (bool, error) {
137-
fileInfo, err := os.Stat(path)
138-
if err != nil {
139-
return false, err
140-
}
141-
return fileInfo.IsDir(), err
142-
}
143-
14445
func Run() {
145-
files, err := doublestar.Glob("./solve/**/*")
146-
if err != nil {
147-
log.Fatal(err)
148-
}
149-
tagMetas := make(TagMetas, 0)
150-
tagMetas["all"] = make(Metas, 0)
151-
wg := sync.WaitGroup{}
152-
var lock sync.Mutex
153-
for _, fp := range files {
154-
if isFolder, _ := isDirectory(fp); isFolder {
155-
continue
156-
}
157-
158-
if strings.HasSuffix(fp, ".md") {
159-
continue
160-
}
161-
wg.Add(1)
162-
fp := fp
163-
go func() {
164-
content, err := ioutil.ReadFile(fp)
165-
if err != nil {
166-
log.Fatal(err)
167-
}
168-
meta := findMeta(content, fp)
169-
if meta != nil {
170-
lock.Lock()
171-
addMeta(tagMetas, meta)
172-
lock.Unlock()
173-
}
174-
wg.Done()
175-
}()
176-
}
177-
wg.Wait()
46+
tagMetas := meta.GetTagMetas()
17847

179-
if !fileExists(toc) {
48+
if !helper.FileExists(toc) {
18049
_ = os.MkdirAll(toc, 0755)
18150
}
18251

52+
wg := sync.WaitGroup{}
53+
18354
for tag, metas := range tagMetas {
18455
fp := filepath.Join(toc, fmt.Sprintf("%s.md", tag))
18556
wg.Add(1)
18657
metas := metas
18758
tag := tag
18859
go func() {
189-
if !fileExists(fp) {
60+
if !helper.FileExists(fp) {
19061
var content bytes.Buffer
19162
err := tagTpl.Execute(&content, &leetcode.Tag{Name: tag})
19263
if err != nil {

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ go 1.15
44

55
require (
66
github.com/bmatcuk/doublestar/v2 v2.0.3
7+
github.com/jomei/notionapi v1.13.0
78
github.com/sashabaranov/go-openai v1.22.0
89
github.com/spf13/viper v1.18.2
910
github.com/stretchr/testify v1.8.4
1011
github.com/tidwall/gjson v1.6.3
12+
golang.org/x/sync v0.5.0
1113
gopkg.in/alecthomas/kingpin.v2 v2.2.6
1214
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,8 @@ github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfE
14191419
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
14201420
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
14211421
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
1422+
github.com/jomei/notionapi v1.13.0 h1:+7c32xWtKJEghIiaKgFg9yBpqh95pxxlqX/CWjqHpkw=
1423+
github.com/jomei/notionapi v1.13.0/go.mod h1:BqzP6JBddpBnXvMSIxiR5dCoCjKngmz5QNl1ONDlDoM=
14221424
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
14231425
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
14241426
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -1848,6 +1850,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
18481850
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
18491851
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
18501852
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
1853+
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
18511854
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
18521855
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
18531856
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

internal/config/config.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ import (
55
"log"
66
)
77

8-
type Gpt struct {
8+
type GptCfg struct {
99
ApiKey string `json:"api_key" mapstructure:"api_key"` // eg. sk-xxxxxxxxxx
1010
Model string `json:"model" mapstructure:"model"` // eg. gpt-3.5-turbo
1111
Prompt string `json:"prompt" mapstructure:"prompt"` // optional
1212
}
1313

14+
type NotionCfg struct {
15+
Token string `json:"token" mapstructure:"token"`
16+
DatabaseID string `json:"database_id" mapstructure:"database_id"`
17+
}
18+
1419
type Config struct {
15-
Lang string `json:"lang" mapstructure:"lang"`
16-
Env string `json:"env" mapstructure:"env"` // eg. en, cn
17-
Gpt *Gpt `json:"gpt" mapstructure:"gpt"`
20+
Lang string `json:"lang" mapstructure:"lang"`
21+
Env string `json:"env" mapstructure:"env"` // eg. en, cn
22+
Gpt *GptCfg `json:"gpt" mapstructure:"gpt"`
23+
Notion *NotionCfg `json:"notion" mapstructure:"notion"`
1824
}
1925

2026
const configPath = ".leetcode.json"

internal/helper/file.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package helper
2+
3+
import "os"
4+
5+
func FileExists(path string) bool {
6+
_, err := os.Stat(path)
7+
return !os.IsNotExist(err)
8+
}
9+
10+
func IsDirectory(path string) (bool, error) {
11+
fileInfo, err := os.Stat(path)
12+
if err != nil {
13+
return false, err
14+
}
15+
return fileInfo.IsDir(), err
16+
}

0 commit comments

Comments
 (0)