diff --git a/internal/base/base.go b/internal/base/base.go
index ad154db77..28168c8c5 100644
--- a/internal/base/base.go
+++ b/internal/base/base.go
@@ -91,15 +91,6 @@ func JSONIndent(src []byte) []byte {
return buf.Bytes()
}
-func getFilePath(filename string) string {
- if dir := filepath.Dir(filename); dir != "" {
- if err := os.MkdirAll(dir, 0755); err != nil {
- CheckErr(err)
- }
- }
- return filename
-}
-
// CheckErr - base.CheckErr
func CheckErr(err error) {
if err != nil {
@@ -117,3 +108,12 @@ func AuthInfo(cmd string) string {
format += "\n"
return fmt.Sprintf(format, cmd, strings.Repeat(" ", 15-len(cmd)))
}
+
+func getFilePath(filename string) string {
+ if dir := filepath.Dir(filename); dir != "" {
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ CheckErr(err)
+ }
+ }
+ return filename
+}
diff --git a/internal/client/client.go b/internal/client/client.go
index df3093a57..b25a8eb32 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -13,18 +13,6 @@ import (
var err error
-func init() {
- http.DefaultClient.Jar, err = cookiejar.New(nil)
- base.CheckErr(err)
- http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
- req.Header.Set("Referer", req.URL.String())
- if len(via) >= 3 {
- return errors.New("stopped after 3 redirects")
- }
- return nil
- }
-}
-
// Get - client.Get
func Get(url string) []byte {
if resp, err := http.Get(url); err == nil {
@@ -46,3 +34,15 @@ func PostJSON(url, jsonStr string) []byte {
}
return nil
}
+
+func init() {
+ http.DefaultClient.Jar, err = cookiejar.New(nil)
+ base.CheckErr(err)
+ http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+ req.Header.Set("Referer", req.URL.String())
+ if len(via) >= 3 {
+ return errors.New("stopped after 3 redirects")
+ }
+ return nil
+ }
+}
diff --git a/internal/leetcode/base.go b/internal/leetcode/base.go
index 2d8996db0..426ce99c9 100644
--- a/internal/leetcode/base.go
+++ b/internal/leetcode/base.go
@@ -24,6 +24,13 @@ var (
translationSet = make(map[int]string)
)
+// Clean - leetcode.Clean
+func Clean() {
+ dir := getCachePath("")
+ err := os.RemoveAll(dir)
+ checkErr(err)
+}
+
func graphQLRequest(graphQL, jsonStr, filename string, days int, v interface{}) {
data := remember(filename, days, func() []byte {
return client.PostJSON(graphQL, jsonStr)
@@ -64,13 +71,6 @@ func getCachePath(f string) string {
return filepath.Join(dir, ".leetcode", f)
}
-// Clean - leetcode.Clean
-func Clean() {
- dir := getCachePath("")
- err := os.RemoveAll(dir)
- checkErr(err)
-}
-
func fileGetContents(filename string) []byte {
if cts, err := ioutil.ReadFile(filename); err == nil {
return cts
diff --git a/internal/leetcode/problems_all.go b/internal/leetcode/problems_all.go
index b72e3c56c..8b40b35b5 100644
--- a/internal/leetcode/problems_all.go
+++ b/internal/leetcode/problems_all.go
@@ -9,18 +9,6 @@ import (
"github.com/openset/leetcode/internal/client"
)
-// ProblemsAll - leetcode.ProblemsAll
-func ProblemsAll() (ps ProblemsType) {
- data := remember(problemsAllFile, 2, func() []byte {
- return client.Get(apiProblemsAllURL)
- })
- jsonDecode(data, &ps)
- sort.Slice(ps.StatStatusPairs, func(i, j int) bool {
- return ps.StatStatusPairs[i].Stat.FrontendQuestionID > ps.StatStatusPairs[j].Stat.FrontendQuestionID
- })
- return
-}
-
// ProblemsType - leetcode.ProblemsType
type ProblemsType struct {
UserName string `json:"user_name"`
@@ -46,6 +34,17 @@ type StatStatusPairsType struct {
Progress int `json:"progress"`
}
+// WriteRow - leetcode.WriteRow
+func (problem *StatStatusPairsType) WriteRow(buf *bytes.Buffer) {
+ format := "| %d | [%s](https://leetcode.com/problems/%s%s)%s | [%s](https://github.com/openset/leetcode/tree/master/problems/%s) | %s |\n"
+ id := problem.Stat.FrontendQuestionID
+ stat := problem.Stat
+ title := strings.TrimSpace(problem.Stat.QuestionTitle)
+ titleSlug := stat.QuestionTitleSlug
+ levelName := problem.Difficulty.LevelName()
+ buf.WriteString(fmt.Sprintf(format, id, id, title, titleSlug, stat.TranslationTitle(), problem.PaidOnly.Str(), stat.Lang(), titleSlug, levelName))
+}
+
type statType struct {
QuestionID int `json:"question_id"`
QuestionArticleLive bool `json:"question__article__live"`
@@ -59,28 +58,8 @@ type statType struct {
IsNewQuestion bool `json:"is_new_question"`
}
-type difficultyType struct {
- Level int `json:"level"`
-}
-
-type paidType bool
-
-// WriteRow - leetcode.WriteRow
-func (problem *StatStatusPairsType) WriteRow(buf *bytes.Buffer) {
- format := "| %d | [%s](https://leetcode.com/problems/%s%s)%s | [%s](https://github.com/openset/leetcode/tree/master/problems/%s) | %s |\n"
- id := problem.Stat.FrontendQuestionID
- stat := problem.Stat
- title := strings.TrimSpace(problem.Stat.QuestionTitle)
- titleSlug := stat.QuestionTitleSlug
- levelName := problem.Difficulty.LevelName()
- buf.WriteString(fmt.Sprintf(format, id, id, title, titleSlug, stat.TranslationTitle(), problem.PaidOnly.Str(), stat.Lang(), titleSlug, levelName))
-}
-
-func (p paidType) Str() string {
- if p {
- return LockStr
- }
- return ""
+func (s *statType) Lang() string {
+ return getLang(s.QuestionTitleSlug)
}
func (s *statType) QuestionTitleSnake() string {
@@ -95,8 +74,8 @@ func (s *statType) TranslationTitle() string {
return title
}
-func (s *statType) Lang() string {
- return getLang(s.QuestionTitleSlug)
+type difficultyType struct {
+ Level int `json:"level"`
}
func (d *difficultyType) LevelName() string {
@@ -107,3 +86,24 @@ func (d *difficultyType) LevelName() string {
}
return m[d.Level]
}
+
+type paidType bool
+
+func (p paidType) Str() string {
+ if p {
+ return LockStr
+ }
+ return ""
+}
+
+// ProblemsAll - leetcode.ProblemsAll
+func ProblemsAll() (ps ProblemsType) {
+ data := remember(problemsAllFile, 2, func() []byte {
+ return client.Get(apiProblemsAllURL)
+ })
+ jsonDecode(data, &ps)
+ sort.Slice(ps.StatStatusPairs, func(i, j int) bool {
+ return ps.StatStatusPairs[i].Stat.FrontendQuestionID > ps.StatStatusPairs[j].Stat.FrontendQuestionID
+ })
+ return
+}
diff --git a/internal/leetcode/problems_status.go b/internal/leetcode/problems_status.go
index 6bb95bd67..6323f9da9 100644
--- a/internal/leetcode/problems_status.go
+++ b/internal/leetcode/problems_status.go
@@ -1,10 +1,5 @@
package leetcode
-// IsSolved - leetcode.IsSolved
-func IsSolved(id int) bool {
- return problemStatus[id]
-}
-
var problemStatus = map[int]bool{
1: true,
2: true,
@@ -229,3 +224,8 @@ var problemStatus = map[int]bool{
1189: true,
1221: true,
}
+
+// IsSolved - leetcode.IsSolved
+func IsSolved(id int) bool {
+ return problemStatus[id]
+}
diff --git a/internal/leetcode/question_data.go b/internal/leetcode/question_data.go
index c03c3721d..7ff06b5d8 100644
--- a/internal/leetcode/question_data.go
+++ b/internal/leetcode/question_data.go
@@ -11,40 +11,30 @@ import (
"strings"
)
-// QuestionData - leetcode.QuestionData
-func QuestionData(titleSlug string, isForce bool, graphQL ...string) (qd QuestionDataType) {
- jsonStr := `{
- "operationName": "questionData",
- "variables": {
- "titleSlug": "` + titleSlug + `"
+const testTpl = `package {{packageName}}
+
+import "testing"
+
+type testType struct {
+ in int
+ want int
+}
+
+func Test{{funcName}}(t *testing.T) {
+ tests := [...]testType{
+ {
+ in: 0,
+ want: 0,
},
- "query": "query questionData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n boundTopicId\n title\n titleSlug\n content\n translatedTitle\n translatedContent\n isPaidOnly\n difficulty\n likes\n dislikes\n isLiked\n similarQuestions\n contributors {\n username\n profileUrl\n avatarUrl\n __typename\n }\n langToValidPlayground\n topicTags {\n name\n slug\n translatedName\n __typename\n }\n companyTagStats\n codeSnippets {\n lang\n langSlug\n code\n __typename\n }\n stats\n hints\n solution {\n id\n canSeeDetail\n __typename\n }\n status\n sampleTestCase\n metaData\n judgerAvailable\n judgeType\n mysqlSchemas\n enableRunCode\n enableTestMode\n envInfo\n __typename\n }\n}\n"
- }`
- days := 3
- if isForce {
- days = 0
- }
- if len(graphQL) == 0 {
- graphQL = []string{graphQLCnURL}
- }
- name := fmt.Sprintf(questionDataFile, slugToSnake(titleSlug))
- filename := getCachePath(name)
- oldContent := getContent(filename)
- graphQLRequest(graphQL[0], jsonStr, name, days, &qd)
- if qd.Data.Question.Content == "" && oldContent != "" {
- qd.Data.Question.Content = oldContent
- filePutContents(filename, jsonEncode(qd))
}
- if qd.Data.Question.TitleSlug == "" {
- if graphQL[0] == graphQLCnURL {
- return QuestionData(titleSlug, true, graphQLUrl)
- }
- for _, err := range qd.Errors {
- log.Println(titleSlug, err.Message)
+ for _, tt := range tests {
+ got := 0
+ if got != tt.want {
+ t.Fatalf("in: %v, got: %v, want: %v", tt.in, got, tt.want)
}
}
- return
}
+`
// QuestionDataType - leetcode.QuestionDataType
type QuestionDataType struct {
@@ -81,32 +71,55 @@ type questionType struct {
MysqlSchemas []string `json:"mysqlSchemas"`
}
-type codeSnippetsType struct {
- Lang string `json:"lang"`
- LangSlug string `json:"langSlug"`
- Code string `json:"code"`
+func (question *questionType) SaveContent() {
+ if question.TitleSlug != "" {
+ filePutContents(question.getFilePath("README.md"), question.getDescContent())
+ question.saveMysqlSchemas()
+ }
}
-type similarQuestionType struct {
- Title string `json:"title"`
- TitleSlug string `json:"titleSlug"`
- Difficulty difficultyStrType `json:"difficulty"`
- TranslatedTitle string `json:"translatedTitle"`
+func (question *questionType) GetSimilarQuestion() (sq []similarQuestionType) {
+ jsonDecode([]byte(question.SimilarQuestions), &sq)
+ return
}
-type difficultyStrType string
+func (question *questionType) TitleSnake() string {
+ return slugToSnake(question.TitleSlug)
+}
-func (d difficultyStrType) Str() (s string) {
- if d != "" {
- s = fmt.Sprintf(" (%s)", d)
- }
- return
+func (question *questionType) LeetCodeURL() string {
+ return "https://leetcode.com/problems/" + question.TitleSlug
}
-func (question *questionType) SaveContent() {
- if question.TitleSlug != "" {
- filePutContents(question.getFilePath("README.md"), question.getDescContent())
- question.saveMysqlSchemas()
+func (question *questionType) PackageName() string {
+ return "problem" + question.QuestionFrontendID
+}
+
+func (question *questionType) SaveCodeSnippet() {
+ if isLangMySQL(question.TitleSlug) {
+ filePutContents(question.getFilePath(question.TitleSnake()+".sql"), []byte("# Write your MySQL query statement below\n"))
+ }
+ langSupport := [...]struct {
+ slug string
+ handle func(*questionType, codeSnippetsType)
+ }{
+ {"golang", handleCodeGolang},
+ {"python3", handleCodePython},
+ {"python", handleCodePython},
+ {"bash", handleCodeBash},
+ {"mysql", handleCodeSQL},
+ {"mssql", handleCodeSQL},
+ {"oraclesql", handleCodeSQL},
+ }
+ codeSet := make(map[string]codeSnippetsType)
+ for _, code := range question.CodeSnippets {
+ codeSet[code.LangSlug] = code
+ }
+ for _, lang := range langSupport {
+ if code, ok := codeSet[lang.slug]; ok {
+ lang.handle(question, code)
+ return
+ }
}
}
@@ -178,9 +191,28 @@ func (question *questionType) getTopicTags() []byte {
return buf.Bytes()
}
-func (question *questionType) GetSimilarQuestion() (sq []similarQuestionType) {
- jsonDecode([]byte(question.SimilarQuestions), &sq)
- return
+func (question *questionType) getHints() []byte {
+ hints := question.Hints
+ var buf bytes.Buffer
+ if len(hints) > 0 {
+ buf.WriteString("\n### Hints")
+ }
+ for i, hint := range hints {
+ buf.WriteString(fmt.Sprintf("\n\nHint %d
\n%s\n \n", i+1, filterContents(hint)))
+ }
+ return buf.Bytes()
+}
+
+func (question *questionType) RenamePackageName() {
+ packageName := fmt.Sprintf("package %s", question.PackageName())
+ reg := regexp.MustCompile(`package \w+`)
+ for _, ext := range [...]string{".go", "_test.go"} {
+ cts := fileGetContents(question.getFilePath(question.TitleSnake() + ext))
+ if len(cts) > 0 {
+ content := reg.ReplaceAllString(string(cts), packageName)
+ question.saveCodeContent(content, ext)
+ }
+ }
}
func (question *questionType) getSimilarQuestion() []byte {
@@ -196,62 +228,10 @@ func (question *questionType) getSimilarQuestion() []byte {
return buf.Bytes()
}
-func (question *questionType) getHints() []byte {
- hints := question.Hints
- var buf bytes.Buffer
- if len(hints) > 0 {
- buf.WriteString("\n### Hints")
- }
- for i, hint := range hints {
- buf.WriteString(fmt.Sprintf("\n\nHint %d
\n%s\n \n", i+1, filterContents(hint)))
- }
- return buf.Bytes()
-}
-
func (question *questionType) getFilePath(filename string) string {
return filepath.Join("problems", question.TitleSlug, filename)
}
-func (question *questionType) TitleSnake() string {
- return slugToSnake(question.TitleSlug)
-}
-
-func (question *questionType) LeetCodeURL() string {
- return "https://leetcode.com/problems/" + question.TitleSlug
-}
-
-func (question *questionType) PackageName() string {
- return "problem" + question.QuestionFrontendID
-}
-
-func (question *questionType) SaveCodeSnippet() {
- if isLangMySQL(question.TitleSlug) {
- filePutContents(question.getFilePath(question.TitleSnake()+".sql"), []byte("# Write your MySQL query statement below\n"))
- }
- langSupport := [...]struct {
- slug string
- handle func(*questionType, codeSnippetsType)
- }{
- {"golang", handleCodeGolang},
- {"python3", handleCodePython},
- {"python", handleCodePython},
- {"bash", handleCodeBash},
- {"mysql", handleCodeSQL},
- {"mssql", handleCodeSQL},
- {"oraclesql", handleCodeSQL},
- }
- codeSet := make(map[string]codeSnippetsType)
- for _, code := range question.CodeSnippets {
- codeSet[code.LangSlug] = code
- }
- for _, lang := range langSupport {
- if code, ok := codeSet[lang.slug]; ok {
- lang.handle(question, code)
- return
- }
- }
-}
-
func (question *questionType) saveCodeContent(content, ext string, permX ...bool) {
filePath := question.getFilePath(question.TitleSnake() + ext)
filePutContents(filePath, []byte(content))
@@ -268,16 +248,61 @@ func (question *questionType) saveMysqlSchemas() {
filePutContents(question.getFilePath("mysql_schemas.sql"), buf.Bytes())
}
-func (question *questionType) RenamePackageName() {
- packageName := fmt.Sprintf("package %s", question.PackageName())
- reg := regexp.MustCompile(`package \w+`)
- for _, ext := range [...]string{".go", "_test.go"} {
- cts := fileGetContents(question.getFilePath(question.TitleSnake() + ext))
- if len(cts) > 0 {
- content := reg.ReplaceAllString(string(cts), packageName)
- question.saveCodeContent(content, ext)
+type codeSnippetsType struct {
+ Lang string `json:"lang"`
+ LangSlug string `json:"langSlug"`
+ Code string `json:"code"`
+}
+
+type similarQuestionType struct {
+ Title string `json:"title"`
+ TitleSlug string `json:"titleSlug"`
+ Difficulty difficultyStrType `json:"difficulty"`
+ TranslatedTitle string `json:"translatedTitle"`
+}
+
+type difficultyStrType string
+
+func (d difficultyStrType) Str() (s string) {
+ if d != "" {
+ s = fmt.Sprintf(" (%s)", d)
+ }
+ return
+}
+
+// QuestionData - leetcode.QuestionData
+func QuestionData(titleSlug string, isForce bool, graphQL ...string) (qd QuestionDataType) {
+ jsonStr := `{
+ "operationName": "questionData",
+ "variables": {
+ "titleSlug": "` + titleSlug + `"
+ },
+ "query": "query questionData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n boundTopicId\n title\n titleSlug\n content\n translatedTitle\n translatedContent\n isPaidOnly\n difficulty\n likes\n dislikes\n isLiked\n similarQuestions\n contributors {\n username\n profileUrl\n avatarUrl\n __typename\n }\n langToValidPlayground\n topicTags {\n name\n slug\n translatedName\n __typename\n }\n companyTagStats\n codeSnippets {\n lang\n langSlug\n code\n __typename\n }\n stats\n hints\n solution {\n id\n canSeeDetail\n __typename\n }\n status\n sampleTestCase\n metaData\n judgerAvailable\n judgeType\n mysqlSchemas\n enableRunCode\n enableTestMode\n envInfo\n __typename\n }\n}\n"
+ }`
+ days := 3
+ if isForce {
+ days = 0
+ }
+ if len(graphQL) == 0 {
+ graphQL = []string{graphQLCnURL}
+ }
+ name := fmt.Sprintf(questionDataFile, slugToSnake(titleSlug))
+ filename := getCachePath(name)
+ oldContent := getContent(filename)
+ graphQLRequest(graphQL[0], jsonStr, name, days, &qd)
+ if qd.Data.Question.Content == "" && oldContent != "" {
+ qd.Data.Question.Content = oldContent
+ filePutContents(filename, jsonEncode(qd))
+ }
+ if qd.Data.Question.TitleSlug == "" {
+ if graphQL[0] == graphQLCnURL {
+ return QuestionData(titleSlug, true, graphQLUrl)
+ }
+ for _, err := range qd.Errors {
+ log.Println(titleSlug, err.Message)
}
}
+ return
}
func handleCodeGolang(question *questionType, code codeSnippetsType) {
@@ -327,28 +352,3 @@ func getContent(filename string) string {
jsonDecode(cts, &qd)
return qd.Data.Question.Content
}
-
-const testTpl = `package {{packageName}}
-
-import "testing"
-
-type testType struct {
- in int
- want int
-}
-
-func Test{{funcName}}(t *testing.T) {
- tests := [...]testType{
- {
- in: 0,
- want: 0,
- },
- }
- for _, tt := range tests {
- got := 0
- if got != tt.want {
- t.Fatalf("in: %v, got: %v, want: %v", tt.in, got, tt.want)
- }
- }
-}
-`
diff --git a/internal/leetcode/question_translation.go b/internal/leetcode/question_translation.go
index 346ad674c..e9fd5a3cf 100644
--- a/internal/leetcode/question_translation.go
+++ b/internal/leetcode/question_translation.go
@@ -6,23 +6,6 @@ import (
"strconv"
)
-// GetQuestionTranslation - leetcode.GetQuestionTranslation
-func GetQuestionTranslation() (qt QuestionTranslationType) {
- jsonStr := `{
- "operationName": "getQuestionTranslation",
- "variables": {},
- "query": "query getQuestionTranslation($lang: String) {\n translations: allAppliedQuestionTranslations(lang: $lang) {\n title\n question {\n questionId\n __typename\n }\n __typename\n }\n}\n"
- }`
- graphQLRequest(graphQLCnURL, jsonStr, questionTranslationFile, 2, &qt)
- if qt.Data.Translations == nil {
- _ = os.Remove(getCachePath(questionTranslationFile))
- for _, err := range qt.Errors {
- log.Println(err.Message)
- }
- }
- return
-}
-
// QuestionTranslationType - leetcode.QuestionTranslationType
type QuestionTranslationType struct {
Errors []errorType `json:"errors"`
@@ -44,6 +27,23 @@ type questionIDType struct {
TypeName string `json:"__typename"`
}
+// GetQuestionTranslation - leetcode.GetQuestionTranslation
+func GetQuestionTranslation() (qt QuestionTranslationType) {
+ jsonStr := `{
+ "operationName": "getQuestionTranslation",
+ "variables": {},
+ "query": "query getQuestionTranslation($lang: String) {\n translations: allAppliedQuestionTranslations(lang: $lang) {\n title\n question {\n questionId\n __typename\n }\n __typename\n }\n}\n"
+ }`
+ graphQLRequest(graphQLCnURL, jsonStr, questionTranslationFile, 2, &qt)
+ if qt.Data.Translations == nil {
+ _ = os.Remove(getCachePath(questionTranslationFile))
+ for _, err := range qt.Errors {
+ log.Println(err.Message)
+ }
+ }
+ return
+}
+
func init() {
translation := GetQuestionTranslation()
for _, item := range translation.Data.Translations {
diff --git a/internal/leetcode/topic_tag.go b/internal/leetcode/topic_tag.go
index 209976664..0cc08dea8 100644
--- a/internal/leetcode/topic_tag.go
+++ b/internal/leetcode/topic_tag.go
@@ -19,81 +19,42 @@ var (
tagsFile = filepath.Join("tag", "tags.json")
)
-func init() {
- html := remember(problemsetAllFile, 3, func() []byte {
- return client.Get(problemsetAllURL)
- })
- reg := regexp.MustCompile(`href="/tag/(\S+?)/"`)
- for _, matches := range reg.FindAllStringSubmatch(string(html), -1) {
- if len(matches) >= 2 {
- initTags = append(initTags, TagType{Slug: matches[1]})
- }
- }
-}
-
-// GetTags - leetcode.GetTags
-func GetTags() (tags []TagType) {
- cts := fileGetContents(tagsFile)
- jsonDecode(cts, &tags)
- tags = tagsUnique(tags)
- return
-}
-
-func saveTags(tags []TagType) {
- base.Mutex.Lock()
- tags = append(GetTags(), tags...)
- filePutContents(tagsFile, jsonEncode(tagsUnique(tags)))
- base.Mutex.Unlock()
+// TagType - leetcode.TagType
+type TagType struct {
+ Name string
+ Slug string
+ TranslatedName string
}
-func tagsUnique(tags []TagType) []TagType {
- rs, top := make([]TagType, 0, len(tags)), 1
- tags = append(initTags, tags...)
- var flag = make(map[string]int)
- for _, tag := range tags {
- i := flag[tag.Slug]
- if i == 0 {
- rs = append(rs, tag)
- flag[tag.Slug] = top
- top++
- } else {
- if tag.Name != "" {
- rs[i-1].Name = tag.Name
- rs[i-1].TranslatedName = tag.Name
- }
- if tag.TranslatedName != "" {
- rs[i-1].TranslatedName = tag.TranslatedName
- }
+// SaveContents - leetcode.SaveContents
+func (tag *TagType) SaveContents() {
+ questions := GetTopicTag(tag.Slug).Data.TopicTag.Questions
+ sort.Slice(questions, func(i, j int) bool {
+ m, _ := strconv.Atoi(questions[i].QuestionFrontendID)
+ n, _ := strconv.Atoi(questions[j].QuestionFrontendID)
+ return m > n
+ })
+ var buf bytes.Buffer
+ buf.WriteString(authInfo("tag"))
+ buf.WriteString(fmt.Sprintf("\n## [话题分类](https://github.com/openset/leetcode/blob/master/tag/README.md) > %s\n\n", tag.name()))
+ buf.WriteString("| # | 题名 | 标签 | 难度 |\n")
+ buf.WriteString("| :-: | - | - | :-: |\n")
+ format := "| %s | [%s](https://github.com/openset/leetcode/tree/master/problems/%s)%s | %s | %s |\n"
+ for _, question := range questions {
+ if question.TranslatedTitle == "" {
+ question.TranslatedTitle = question.Title
}
+ buf.WriteString(fmt.Sprintf(format, question.QuestionFrontendID, question.TranslatedTitle, question.TitleSlug, question.IsPaidOnly.Str(), question.TagsStr(), question.Difficulty))
}
- return rs
+ filename := filepath.Join("tag", tag.Slug, "README.md")
+ filePutContents(filename, buf.Bytes())
}
-// GetTopicTag - leetcode.GetTopicTag
-func GetTopicTag(slug string) (tt TopicTagType) {
- jsonStr := `{
- "operationName": "getTopicTag",
- "variables": {
- "slug": "` + slug + `"
- },
- "query": "query getTopicTag($slug: String!) {\n topicTag(slug: $slug) {\n name\n translatedName\n questions {\n status\n questionId\n questionFrontendId\n title\n titleSlug\n translatedTitle\n stats\n difficulty\n isPaidOnly\n topicTags {\n name\n translatedName\n slug\n __typename\n }\n __typename\n }\n frequencies\n __typename\n }\n favoritesLists {\n publicFavorites {\n ...favoriteFields\n __typename\n }\n privateFavorites {\n ...favoriteFields\n __typename\n }\n __typename\n }\n}\n\nfragment favoriteFields on FavoriteNode {\n idHash\n id\n name\n isPublicFavorite\n viewCount\n creator\n isWatched\n questions {\n questionId\n title\n titleSlug\n __typename\n }\n __typename\n}\n"
- }`
- filename := fmt.Sprintf(topicTagFile, slugToSnake(slug))
- graphQLRequest(graphQLCnURL, jsonStr, filename, 2, &tt)
- if tt.Data.TopicTag.Name == "" {
- _ = os.Remove(getCachePath(filename))
- for _, err := range tt.Errors {
- log.Println(slug, err.Message)
- }
+func (tag *TagType) name() string {
+ if tag.TranslatedName != "" {
+ return tag.TranslatedName
}
- return
-}
-
-// TagType - leetcode.TagType
-type TagType struct {
- Name string
- Slug string
- TranslatedName string
+ return tag.Name
}
// TopicTagType - leetcode.TopicTagType
@@ -134,33 +95,72 @@ func (question *ttQuestionType) TagsStr() string {
return buf.String()
}
-// SaveContents - leetcode.SaveContents
-func (tag *TagType) SaveContents() {
- questions := GetTopicTag(tag.Slug).Data.TopicTag.Questions
- sort.Slice(questions, func(i, j int) bool {
- m, _ := strconv.Atoi(questions[i].QuestionFrontendID)
- n, _ := strconv.Atoi(questions[j].QuestionFrontendID)
- return m > n
- })
- var buf bytes.Buffer
- buf.WriteString(authInfo("tag"))
- buf.WriteString(fmt.Sprintf("\n## [话题分类](https://github.com/openset/leetcode/blob/master/tag/README.md) > %s\n\n", tag.name()))
- buf.WriteString("| # | 题名 | 标签 | 难度 |\n")
- buf.WriteString("| :-: | - | - | :-: |\n")
- format := "| %s | [%s](https://github.com/openset/leetcode/tree/master/problems/%s)%s | %s | %s |\n"
- for _, question := range questions {
- if question.TranslatedTitle == "" {
- question.TranslatedTitle = question.Title
+// GetTags - leetcode.GetTags
+func GetTags() (tags []TagType) {
+ cts := fileGetContents(tagsFile)
+ jsonDecode(cts, &tags)
+ tags = tagsUnique(tags)
+ return
+}
+
+// GetTopicTag - leetcode.GetTopicTag
+func GetTopicTag(slug string) (tt TopicTagType) {
+ jsonStr := `{
+ "operationName": "getTopicTag",
+ "variables": {
+ "slug": "` + slug + `"
+ },
+ "query": "query getTopicTag($slug: String!) {\n topicTag(slug: $slug) {\n name\n translatedName\n questions {\n status\n questionId\n questionFrontendId\n title\n titleSlug\n translatedTitle\n stats\n difficulty\n isPaidOnly\n topicTags {\n name\n translatedName\n slug\n __typename\n }\n __typename\n }\n frequencies\n __typename\n }\n favoritesLists {\n publicFavorites {\n ...favoriteFields\n __typename\n }\n privateFavorites {\n ...favoriteFields\n __typename\n }\n __typename\n }\n}\n\nfragment favoriteFields on FavoriteNode {\n idHash\n id\n name\n isPublicFavorite\n viewCount\n creator\n isWatched\n questions {\n questionId\n title\n titleSlug\n __typename\n }\n __typename\n}\n"
+ }`
+ filename := fmt.Sprintf(topicTagFile, slugToSnake(slug))
+ graphQLRequest(graphQLCnURL, jsonStr, filename, 2, &tt)
+ if tt.Data.TopicTag.Name == "" {
+ _ = os.Remove(getCachePath(filename))
+ for _, err := range tt.Errors {
+ log.Println(slug, err.Message)
}
- buf.WriteString(fmt.Sprintf(format, question.QuestionFrontendID, question.TranslatedTitle, question.TitleSlug, question.IsPaidOnly.Str(), question.TagsStr(), question.Difficulty))
}
- filename := filepath.Join("tag", tag.Slug, "README.md")
- filePutContents(filename, buf.Bytes())
+ return
}
-func (tag *TagType) name() string {
- if tag.TranslatedName != "" {
- return tag.TranslatedName
+func saveTags(tags []TagType) {
+ base.Mutex.Lock()
+ tags = append(GetTags(), tags...)
+ filePutContents(tagsFile, jsonEncode(tagsUnique(tags)))
+ base.Mutex.Unlock()
+}
+
+func tagsUnique(tags []TagType) []TagType {
+ rs, top := make([]TagType, 0, len(tags)), 1
+ tags = append(initTags, tags...)
+ var flag = make(map[string]int)
+ for _, tag := range tags {
+ i := flag[tag.Slug]
+ if i == 0 {
+ rs = append(rs, tag)
+ flag[tag.Slug] = top
+ top++
+ } else {
+ if tag.Name != "" {
+ rs[i-1].Name = tag.Name
+ rs[i-1].TranslatedName = tag.Name
+ }
+ if tag.TranslatedName != "" {
+ rs[i-1].TranslatedName = tag.TranslatedName
+ }
+ }
+ }
+ return rs
+}
+
+func init() {
+ html := remember(problemsetAllFile, 3, func() []byte {
+ return client.Get(problemsetAllURL)
+ })
+ reg := regexp.MustCompile(`href="/tag/(\S+?)/"`)
+ for _, matches := range reg.FindAllStringSubmatch(string(html), -1) {
+ if len(matches) >= 2 {
+ initTags = append(initTags, TagType{Slug: matches[1]})
+ }
}
- return tag.Name
}
diff --git a/internal/post/post.go b/internal/post/post.go
index cc7d0f46f..25fa4c8f8 100644
--- a/internal/post/post.go
+++ b/internal/post/post.go
@@ -14,6 +14,24 @@ import (
"github.com/openset/leetcode/internal/leetcode"
)
+const frontMatter = `---
+layout: single
+title: "%s"
+date: %s +0800
+categories: [Leetcode]
+tags: [%s]
+permalink: /problems/%s/
+---
+`
+
+const tagTmpl = `---
+title: "%s"
+layout: tag
+permalink: /tags/%s/
+taxonomy: %s
+---
+`
+
// CmdPost - post.CmdPost
var CmdPost = &base.Command{
Run: runPost,
@@ -23,6 +41,11 @@ var CmdPost = &base.Command{
Hidden: true,
}
+var (
+ homeDir, _ = os.UserHomeDir()
+ basePath = filepath.Join(homeDir, "openset", "openset")
+)
+
func runPost(cmd *base.Command, args []string) {
if len(args) != 0 {
cmd.Usage()
@@ -37,7 +60,7 @@ func runPost(cmd *base.Command, args []string) {
titleSlug := problem.Stat.QuestionTitleSlug
question := leetcode.QuestionData(titleSlug, false).Data.Question
if question.TranslatedContent != "" {
- fmt.Println(id, "\t"+question.TranslatedTitle, "saving...")
+ fmt.Println(id, "\t"+question.TranslatedTitle)
var buf bytes.Buffer
t := time.Date(2016, 1, 1, 21, 30, 0, 0, time.Local)
t = t.AddDate(0, 0, id)
@@ -107,26 +130,3 @@ func postTags() {
base.FilePutContents(filepath.Join(basePath, "_pages", filename), data)
}
}
-
-const frontMatter = `---
-layout: single
-title: "%s"
-date: %s +0800
-categories: [Leetcode]
-tags: [%s]
-permalink: /problems/%s/
----
-`
-
-const tagTmpl = `---
-title: "%s"
-layout: tag
-permalink: /tags/%s/
-taxonomy: %s
----
-`
-
-var (
- homeDir, _ = os.UserHomeDir()
- basePath = filepath.Join(homeDir, "openset", "openset")
-)
diff --git a/internal/readme/readme.go b/internal/readme/readme.go
index 34d5889ab..9b906dfa9 100644
--- a/internal/readme/readme.go
+++ b/internal/readme/readme.go
@@ -10,6 +10,21 @@ import (
"github.com/openset/leetcode/internal/leetcode"
)
+const defaultStr = `
+# [LeetCode](https://openset.github.io/leetcode)
+LeetCode Problems' Solutions
+[[力扣](https://openset.github.io/categories/leetcode/) ∙ [话题分类](https://github.com/openset/leetcode/blob/master/tag/README.md)]
+
+[](https://travis-ci.org/openset/leetcode)
+[](https://codecov.io/gh/openset/leetcode)
+[](https://goreportcard.com/report/github.com/openset/leetcode)
+[](https://github.com/openset/leetcode/graphs/contributors)
+[](https://github.com/openset/leetcode/blob/master/LICENSE)
+[](https://app.fossa.io/projects/git%2Bgithub.com%2Fopenset%2Fleetcode?ref=badge_shield)
+[](https://gitter.im/openset/leetcode?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+`
+
// CmdReadme - readme.CmdReadme
var CmdReadme = &base.Command{
Run: runReadme,
@@ -18,6 +33,15 @@ var CmdReadme = &base.Command{
Long: "build README.md file.",
}
+var (
+ buildCmd = "readme"
+ fileName = "README.md"
+ maxID = 0
+ pageSize = 300
+ step = 50
+ num = 6
+)
+
func runReadme(cmd *base.Command, args []string) {
if len(args) == 1 && args[0] == "page" {
buildCmd = "page"
@@ -82,27 +106,3 @@ func linkStr(num int) string {
}
return link
}
-
-var (
- buildCmd = "readme"
- fileName = "README.md"
- maxID = 0
- pageSize = 300
- step = 50
- num = 6
-)
-
-var defaultStr = `
-# [LeetCode](https://openset.github.io/leetcode)
-LeetCode Problems' Solutions
-[[力扣](https://openset.github.io/categories/leetcode/) ∙ [话题分类](https://github.com/openset/leetcode/blob/master/tag/README.md)]
-
-[](https://travis-ci.org/openset/leetcode)
-[](https://codecov.io/gh/openset/leetcode)
-[](https://goreportcard.com/report/github.com/openset/leetcode)
-[](https://github.com/openset/leetcode/graphs/contributors)
-[](https://github.com/openset/leetcode/blob/master/LICENSE)
-[](https://app.fossa.io/projects/git%2Bgithub.com%2Fopenset%2Fleetcode?ref=badge_shield)
-[](https://gitter.im/openset/leetcode?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-
-`
diff --git a/internal/tag/tag.go b/internal/tag/tag.go
index 00ccea6d8..6ceb295a1 100644
--- a/internal/tag/tag.go
+++ b/internal/tag/tag.go
@@ -37,7 +37,7 @@ func runTag(cmd *base.Command, args []string) {
tags := leetcode.GetTags()
for i, tag := range tags {
if tag.Name != "" {
- fmt.Println(i+1, "\t"+tag.Name, "saving...")
+ fmt.Println(i+1, "\t"+tag.Name)
}
buf.WriteString(fmt.Sprintf(format, i+1, tag.Name, tag.Slug, tag.TranslatedName, tag.Slug))
if i&1 == 1 {