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)] + +[![Build Status](https://travis-ci.org/openset/leetcode.svg?branch=master)](https://travis-ci.org/openset/leetcode) +[![codecov](https://codecov.io/gh/openset/leetcode/branch/master/graph/badge.svg)](https://codecov.io/gh/openset/leetcode) +[![Go Report Card](https://goreportcard.com/badge/github.com/openset/leetcode)](https://goreportcard.com/report/github.com/openset/leetcode) +[![GitHub contributors](https://img.shields.io/github/contributors/openset/leetcode.svg)](https://github.com/openset/leetcode/graphs/contributors) +[![license](https://img.shields.io/github/license/openset/leetcode.svg)](https://github.com/openset/leetcode/blob/master/LICENSE) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fopenset%2Fleetcode.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fopenset%2Fleetcode?ref=badge_shield) +[![Join the chat](https://badges.gitter.im/openset/leetcode.svg)](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)] - -[![Build Status](https://travis-ci.org/openset/leetcode.svg?branch=master)](https://travis-ci.org/openset/leetcode) -[![codecov](https://codecov.io/gh/openset/leetcode/branch/master/graph/badge.svg)](https://codecov.io/gh/openset/leetcode) -[![Go Report Card](https://goreportcard.com/badge/github.com/openset/leetcode)](https://goreportcard.com/report/github.com/openset/leetcode) -[![GitHub contributors](https://img.shields.io/github/contributors/openset/leetcode.svg)](https://github.com/openset/leetcode/graphs/contributors) -[![license](https://img.shields.io/github/license/openset/leetcode.svg)](https://github.com/openset/leetcode/blob/master/LICENSE) -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fopenset%2Fleetcode.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fopenset%2Fleetcode?ref=badge_shield) -[![Join the chat](https://badges.gitter.im/openset/leetcode.svg)](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 {