Skip to content

Commit

Permalink
impl
Browse files Browse the repository at this point in the history
  • Loading branch information
j178 committed Jan 29, 2023
1 parent fea9041 commit 41a060f
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 123 deletions.
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@

## 主要特性

- 通过简单、漂亮的 UI 来挑选、过滤题目
- 为题目生成描述、样例代码、测试代码,支持本地测试
- 自动等待并及时生成所有竞赛题目
- 自动为题目生成描述、样例代码、测试代码
- 通过模板引擎自定义配置生成的代码文件,支持对代码做预处理
- 支持本地测试,可以使用 Debugger 调试代码
- 自动等待并及时生成竞赛题目,一键提交所有题目
- (即将) 同时支持 leetcode.com (美国站) 和 leetcode.cn (中国站)
- 自动从浏览器中读取 LeetCode 的 Cookie,无需手动提供

Expand Down Expand Up @@ -149,10 +150,26 @@ code:
# Available attributes: Id, Slug, Title, Difficulty, Lang, SlugIsMeaningful
# Available functions: lower, upper, trim, padWithZero, toUnderscore
filename_template: '{{ .Id | padWithZero 4 }}{{ if .SlugIsMeaningful }}.{{ .Slug }}{{ end }}'
# TODO
modifiers:
- name: removeUselessComments
go:
out_dir: go
# Overrides the default code.filename_template
filename_template: ""
# TODO
blocks:
- name: beforeMarker
template: |
package main
{{ if .NeedsDefinition -}} import . "github.com/j178/leetgo/testutils/go" {{- end }}
# TODO
modifiers:
- name: removeUselessComments
- name: changeReceiverName
- name: addMod
- name: addNamedReturn
# Go module path for the generated code
go_mod_path: ""
python3:
Expand Down
16 changes: 16 additions & 0 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,26 @@ code:
# Available attributes: Id, Slug, Title, Difficulty, Lang, SlugIsMeaningful
# Available functions: lower, upper, trim, padWithZero, toUnderscore
filename_template: '{{ .Id | padWithZero 4 }}{{ if .SlugIsMeaningful }}.{{ .Slug }}{{ end }}'
# TODO
modifiers:
- name: removeUselessComments
go:
out_dir: go
# Overrides the default code.filename_template
filename_template: ""
# TODO
blocks:
- name: beforeMarker
template: |
package main
{{ if .NeedsDefinition -}} import . "github.com/j178/leetgo/testutils/go" {{- end }}
# TODO
modifiers:
- name: removeUselessComments
- name: changeReceiverName
- name: addMod
- name: addNamedReturn
# Go module path for the generated code
go_mod_path: ""
python3:
Expand Down
42 changes: 39 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
stateFile = "cache/state.json"
CodeBeginMarker = "@lc code=begin"
CodeEndMarker = "@lc code=end"
GoTestUtilsModPath = "github.com/j178/leetgo/testutils/go"
)

var (
Expand Down Expand Up @@ -64,9 +65,21 @@ type Editor struct {
Args []string `yaml:"args" mapstructure:"args" comment:"Arguments to the command"`
}

type Block struct {
Name string `yaml:"name" mapstructure:"name"`
Template string `yaml:"template" mapstructure:"template"`
}

type Modifier struct {
Name string `yaml:"name" mapstructure:"name"`
Func string `yaml:"func,omitempty" mapstructure:"func"`
}

type CodeConfig struct {
Lang string `yaml:"lang" mapstructure:"lang" comment:"Language of code generated for questions: go, python, ... \n(will be override by project config and flag --lang)"`
FilenameTemplate string `yaml:"filename_template" mapstructure:"filename_template" comment:"The default template to generate filename (without extension), e.g. {{.Id}}.{{.Slug}}\nAvailable attributes: Id, Slug, Title, Difficulty, Lang, SlugIsMeaningful\nAvailable functions: lower, upper, trim, padWithZero, toUnderscore"`
Blocks []Block `yaml:"blocks,omitempty" mapstructure:"blocks" comment:"TODO"`
Modifiers []Modifier `yaml:"modifiers,omitempty" mapstructure:"modifiers" comment:"TODO"`
Go GoConfig `yaml:"go" mapstructure:"go"`
Python BaseLangConfig `yaml:"python3" mapstructure:"python3"`
Cpp BaseLangConfig `yaml:"cpp" mapstructure:"cpp"`
Expand All @@ -76,8 +89,10 @@ type CodeConfig struct {
}

type BaseLangConfig struct {
OutDir string `yaml:"out_dir" mapstructure:"out_dir"`
FilenameTemplate string `yaml:"filename_template" mapstructure:"filename_template" comment:"Overrides the default code.filename_template"`
OutDir string `yaml:"out_dir" mapstructure:"out_dir"`
FilenameTemplate string `yaml:"filename_template" mapstructure:"filename_template" comment:"Overrides the default code.filename_template"`
Blocks []Block `yaml:"blocks,omitempty" mapstructure:"blocks" comment:"TODO"`
Modifiers []Modifier `yaml:"modifiers,omitempty" mapstructure:"modifiers" comment:"TODO"`
}

type GoConfig struct {
Expand Down Expand Up @@ -167,8 +182,29 @@ func Default() *Config {
Code: CodeConfig{
Lang: "go",
FilenameTemplate: `{{ .Id | padWithZero 4 }}{{ if .SlugIsMeaningful }}.{{ .Slug }}{{ end }}`,
Modifiers: []Modifier{
{Name: "removeUselessComments"},
},
Go: GoConfig{
BaseLangConfig: BaseLangConfig{OutDir: "go"},
BaseLangConfig: BaseLangConfig{
OutDir: "go",
Blocks: []Block{
{
Name: "beforeMarker", Template: fmt.Sprintf(
`package main
{{ if .NeedsDefinition -}} import . "%s" {{- end }}
`, GoTestUtilsModPath,
),
},
},
Modifiers: []Modifier{
{Name: "removeUselessComments"},
{Name: "changeReceiverName"},
{Name: "addMod"},
{Name: "addNamedReturn"},
},
},
},
Python: BaseLangConfig{OutDir: "python"},
Cpp: BaseLangConfig{OutDir: "cpp"},
Expand Down
93 changes: 58 additions & 35 deletions lang/gen.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package lang

import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"time"

"github.com/AlecAivazis/survey/v2"
Expand Down Expand Up @@ -102,31 +104,56 @@ func (l baseLang) LineComment() string {
return l.lineComment
}

func (l baseLang) generateComments(q *leetcode.QuestionData) string {
var content []string
cfg := config.Get()
now := time.Now().Format("2006/01/02 15:04")
content = append(content, fmt.Sprintf("%s Created by %s at %s", l.lineComment, cfg.Author, now))
content = append(content, fmt.Sprintf("%s %s", l.lineComment, q.Url()))
if q.IsContest() {
content = append(content, fmt.Sprintf("%s %s", l.lineComment, q.ContestUrl()))
}
content = append(content, "")
content = append(content, l.blockCommentStart)
content = append(content, fmt.Sprintf("%s. %s (%s)", q.QuestionFrontendId, q.GetTitle(), q.Difficulty))
content = append(content, "")
content = append(content, q.GetFormattedContent())
content = append(content, l.blockCommentEnd)
content = append(content, "")
return strings.Join(content, "\n")
}

func (l baseLang) generateCode(q *leetcode.QuestionData, modifiers ...Modifier) string {
func (l baseLang) generateContent(q *leetcode.QuestionData, blocks []config.Block, modifiers []ModifierFunc) (
string,
error,
) {
code := q.GetCodeSnippet(l.Slug())
for _, m := range modifiers {
code = m(code, q)
tmpl := template.New("root")
tmpl.Funcs(
template.FuncMap{
"runModifiers": func(code string) string {
for _, m := range modifiers {
code = m(code, q)
}
return code
},
},
)
_, err := tmpl.Parse(contentTemplate)
if err != nil {
return "", err
}
return code

for _, block := range blocks {
if _, ok := validBlocks[block.Name]; !ok {
return "", fmt.Errorf("invalid block name: %s", block.Name)
}
_, err := tmpl.New(block.Name).Parse(block.Template)
if err != nil {
return "", err
}
}

cfg := config.Get()
data := &contentData{
Question: q,
Author: cfg.Author,
Time: time.Now().Format("2006/01/02 15:04"),
LineComment: l.lineComment,
BlockCommentStart: l.blockCommentStart,
BlockCommentEnd: l.blockCommentEnd,
CodeBeginMarker: config.CodeBeginMarker,
CodeEndMarker: config.CodeEndMarker,
Code: code,
NeedsDefinition: needsDefinition(code),
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}

func (l baseLang) generateTestCases(q *leetcode.QuestionData) string {
Expand Down Expand Up @@ -170,9 +197,12 @@ func (l baseLang) GeneratePaths(q *leetcode.QuestionData) (*GenerateResult, erro
}

func (l baseLang) Generate(q *leetcode.QuestionData) (*GenerateResult, error) {
comment := l.generateComments(q)
code := l.generateCode(q, addCodeMarker(l))
content := comment + "\n" + code + "\n"
blocks := getBlocks(l)
modifiers := getModifiers(l, builtinModifiers)
content, err := l.generateContent(q, blocks, modifiers)
if err != nil {
return nil, err
}

filenameTmpl := getFilenameTemplate(q, l)
baseFilename, err := q.GetFormattedFilename(l.slug, filenameTmpl)
Expand Down Expand Up @@ -220,14 +250,6 @@ func getCodeConfig(lang Lang, key string) string {
return viper.GetString("code." + lang.ShortName() + "." + key)
}

func needsDefinition(code string) bool {
return strings.Contains(code, "Definition for")
}

func needsMod(description string) bool {
return strings.Contains(description, "10⁹ + 7")
}

func getFilenameTemplate(q *leetcode.QuestionData, gen Lang) string {
if q.IsContest() {
return config.Get().Contest.FilenameTemplate
Expand All @@ -245,6 +267,7 @@ func getOutDir(q *leetcode.QuestionData, lang Lang) string {
}
cfg := config.Get()
outDir := getCodeConfig(lang, "out_dir")
// If outDir is not set, use the language slug as the outDir.
if outDir == "" {
outDir = lang.Slug()
}
Expand All @@ -266,7 +289,7 @@ func generate(q *leetcode.QuestionData) (Lang, *GenerateResult, error) {

codeSnippet := q.GetCodeSnippet(gen.Slug())
if codeSnippet == "" {
return nil, nil, fmt.Errorf("no %s code snippet found for %s", cfg.Code.Lang, q.TitleSlug)
return nil, nil, fmt.Errorf(`question "%s" doesn't support using "%s"`, cfg.Code.Lang, q.TitleSlug)
}

outDir := getOutDir(q, gen)
Expand Down
Loading

0 comments on commit 41a060f

Please sign in to comment.