From 7ea50ef604ef98f251b4fc4e6694cb205d27ecc2 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Sun, 10 Dec 2023 18:35:22 +0800 Subject: [PATCH] tmp --- cmd/root.go | 2 +- config/config.go | 32 +++-- lang/base.go | 10 -- lang/cpp.go | 12 +- lang/java.go | 126 +++++++++++++++--- leetcode/question.go | 4 +- testutils/java/.mvn/maven.config | 3 + .../.mvn/wrapper/maven-wrapper.properties | 2 +- .../src/main/java/io/github/j178/abc/A.java | 4 + 9 files changed, 149 insertions(+), 46 deletions(-) create mode 100644 testutils/java/.mvn/maven.config create mode 100644 testutils/java/src/main/java/io/github/j178/abc/A.java diff --git a/cmd/root.go b/cmd/root.go index 3289fc99..ea121e1e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -103,7 +103,7 @@ func initCommands() { rootCmd.SetOut(os.Stdout) rootCmd.InitDefaultVersionFlag() rootCmd.Flags().SortFlags = false - rootCmd.PersistentFlags().StringP("lang", "l", "", "language of code to generate: cpp, go, python ...") + rootCmd.PersistentFlags().StringP("lang", "l", "", "language of code to generate: cpp, go, python, java...") rootCmd.PersistentFlags().StringP("site", "", "", "leetcode site: cn, us") rootCmd.PersistentFlags().BoolP("yes", "y", false, "answer yes to all prompts") rootCmd.InitDefaultHelpFlag() diff --git a/config/config.go b/config/config.go index 63fdf0db..d40806c6 100644 --- a/config/config.go +++ b/config/config.go @@ -70,16 +70,16 @@ type Modifier struct { } 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, group"` - SeparateDescriptionFile bool `yaml:"separate_description_file" mapstructure:"separate_description_file" comment:"Generate question description into a separate question.md file"` - Blocks []Block `yaml:"blocks,omitempty" mapstructure:"blocks" comment:"Default block definitions for all languages"` - Modifiers []Modifier `yaml:"modifiers,omitempty" mapstructure:"modifiers" comment:"Default modifiers for all languages"` - Go GoConfig `yaml:"go" mapstructure:"go"` - Python PythonConfig `yaml:"python3" mapstructure:"python3"` - Cpp CppConfig `yaml:"cpp" mapstructure:"cpp"` - Rust RustConfig `yaml:"rust" mapstructure:"rust"` - Java BaseLangConfig `yaml:"java" mapstructure:"java"` + 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, group"` + SeparateDescriptionFile bool `yaml:"separate_description_file" mapstructure:"separate_description_file" comment:"Generate question description into a separate question.md file"` + Blocks []Block `yaml:"blocks,omitempty" mapstructure:"blocks" comment:"Default block definitions for all languages"` + Modifiers []Modifier `yaml:"modifiers,omitempty" mapstructure:"modifiers" comment:"Default modifiers for all languages"` + Go GoConfig `yaml:"go" mapstructure:"go"` + Python PythonConfig `yaml:"python3" mapstructure:"python3"` + Cpp CppConfig `yaml:"cpp" mapstructure:"cpp"` + Rust RustConfig `yaml:"rust" mapstructure:"rust"` + Java JavaConfig `yaml:"java" mapstructure:"java"` // Add more languages here } @@ -110,6 +110,11 @@ type RustConfig struct { BaseLangConfig `yaml:",inline" mapstructure:",squash"` } +type JavaConfig struct { + BaseLangConfig `yaml:",inline" mapstructure:",squash"` + GroupID string `yaml:"group_id" mapstructure:"group_id" comment:"Maven group ID"` +} + type Credentials struct { From string `yaml:"from" mapstructure:"from" comment:"How to provide credentials: browser, cookies, password or none"` Browsers []string `yaml:"browsers" mapstructure:"browsers" comment:"Browsers to get cookies from: chrome, safari, edge or firefox. If empty, all browsers will be tried"` @@ -223,7 +228,12 @@ func defaultConfig() *Config { BaseLangConfig: BaseLangConfig{OutDir: "python"}, Executable: constants.DefaultPython, }, - Java: BaseLangConfig{OutDir: "java"}, + Java: JavaConfig{ + BaseLangConfig: BaseLangConfig{ + OutDir: "java", + FilenameTemplate: `p{{ .Id | padWithZero 4 }}{{ if .SlugIsMeaningful }}_{{ .Slug | lower | toUnderscore }}{{ end }}`, + }, + }, Rust: RustConfig{BaseLangConfig: BaseLangConfig{OutDir: "rust"}}, // Add more languages here }, diff --git a/lang/base.go b/lang/base.go index d8e9c20e..ef9c605a 100644 --- a/lang/base.go +++ b/lang/base.go @@ -167,16 +167,6 @@ func getTempBinFile(q *leetcode.QuestionData, lang Lang) (string, error) { return filepath.Join(tmpDir, filename), nil } -func getTempBinDir(q *leetcode.QuestionData, lang Lang) (string, error) { - tmpDir := config.Get().TempDir() - dirname := fmt.Sprintf("%s-%s", q.TitleSlug, lang.Slug()) - dir := filepath.Join(tmpDir, dirname) - if err := utils.CreateIfNotExists(dir, true); err != nil { - return "", err - } - return dir, nil -} - func separateDescriptionFile(lang Lang) bool { ans := viper.Get("code." + lang.Slug() + ".separate_description_file") if ans != nil { diff --git a/lang/cpp.go b/lang/cpp.go index 1b3efb5d..84502ba3 100644 --- a/lang/cpp.go +++ b/lang/cpp.go @@ -10,7 +10,7 @@ import ( "github.com/j178/leetgo/config" "github.com/j178/leetgo/constants" "github.com/j178/leetgo/leetcode" - cppUtils "github.com/j178/leetgo/testutils/cpp" + cppEmbed "github.com/j178/leetgo/testutils/cpp" "github.com/j178/leetgo/utils" ) @@ -19,13 +19,13 @@ type cpp struct { } func (c cpp) Initialize(outDir string) error { - headerPath := filepath.Join(outDir, cppUtils.HeaderName) - err := utils.WriteFile(headerPath, cppUtils.HeaderContent) + headerPath := filepath.Join(outDir, cppEmbed.HeaderName) + err := utils.WriteFile(headerPath, cppEmbed.HeaderContent) if err != nil { return err } stdCxxPath := filepath.Join(outDir, "bits", "stdc++.h") - err = utils.WriteFile(stdCxxPath, cppUtils.StdCxxContent) + err = utils.WriteFile(stdCxxPath, cppEmbed.StdCxxContent) if err != nil { return err } @@ -33,7 +33,7 @@ func (c cpp) Initialize(outDir string) error { } func (c cpp) HasInitialized(outDir string) (bool, error) { - headerPath := filepath.Join(outDir, cppUtils.HeaderName) + headerPath := filepath.Join(outDir, cppEmbed.HeaderName) if !utils.IsExist(headerPath) { return false, nil } @@ -278,7 +278,7 @@ func (c cpp) generateCodeFile( #include "%s" using namespace std; -`, cppUtils.HeaderName, +`, cppEmbed.HeaderName, ) testContent, err := c.generateTestContent(q) if err != nil { diff --git a/lang/java.go b/lang/java.go index d8b33a44..d074d20d 100644 --- a/lang/java.go +++ b/lang/java.go @@ -2,21 +2,113 @@ package lang import ( "fmt" + "path/filepath" + "regexp" + "runtime" + "strings" "github.com/j178/leetgo/config" "github.com/j178/leetgo/leetcode" + javaEmbed "github.com/j178/leetgo/testutils/java" "github.com/j178/leetgo/utils" ) +const ( + pomTemplate = ` + + 4.0.0 + + %s + %s + 1.0 + jar + + leetcode-solutions + + + UTF-8 + + + + + io.github.j178 + leetgo-java + 1.0 + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.1 + + + + + +` +) + type java struct { baseLang } +func mvnwCmd(dir string, args ...string) []string { + cmdName := "mvnw" + if runtime.GOOS == "windows" { + cmdName = "mvnw.cmd" + } + return append([]string{filepath.Join(dir, cmdName), "-q"}, args...) +} + func (j java) HasInitialized(outDir string) (bool, error) { - return false, nil + return utils.IsExist(filepath.Join(outDir, "pom.xml")), nil +} + +var nameReplacer = regexp.MustCompile(`[^a-zA-Z0-9_]`) + +func (j java) groupID() string { + cfg := config.Get() + if cfg.Code.Java.GroupID != "" { + return cfg.Code.Java.GroupID + } + name := nameReplacer.ReplaceAllString(strings.ToLower(cfg.Author), "_") + return "io.github." + name +} + +func (j java) sourceDir() string { + groupID := j.groupID() + path := []string{"src", "main", "java"} + path = append(path, strings.Split(groupID, ".")...) + return filepath.Join(path...) } func (j java) Initialize(outDir string) error { + // Copy mvn wrapper from embed + err := utils.CopyFS(outDir, javaEmbed.MvnWrapper) + if err != nil { + return err + } + + groupID := j.groupID() + artifactID := "leetcode-solutions" + + // Write pom.xml + pomXML := fmt.Sprintf(pomTemplate, groupID, artifactID) + err = utils.WriteFile(filepath.Join(outDir, "pom.xml"), []byte(pomXML)) + if err != nil { + return err + } + + // Create layout + sourceDir := filepath.Join(outDir, j.sourceDir()) + err = utils.MakeDir(sourceDir) + if err != nil { + return err + } return nil } @@ -32,19 +124,15 @@ func (j java) RunLocalTest(q *leetcode.QuestionData, outDir string, targetCase s return false, fmt.Errorf("file %s not found", utils.RelToCwd(testFile)) } - execDir, err := getTempBinDir(q, j) - if err != nil { - return false, fmt.Errorf("get temp bin dir failed: %w", err) - } - - args := []string{"javac", "-d", execDir, testFile} - err = buildTest(q, genResult, args) + buildCmd := mvnwCmd(outDir, "compile") + err = buildTest(q, genResult, buildCmd) if err != nil { return false, fmt.Errorf("build failed: %w", err) } - args = []string{"java", "--class-path", execDir, "Main"} - return runTest(q, genResult, args, targetCase) + // TODO 生成 package name, 后续执行需要用这个名字拼成的完整 main class + execCmd := mvnwCmd(outDir, "exec:exec", fmt.Sprintf("-Dexec.mainClass=%s.Main")) + return runTest(q, genResult, execCmd, targetCase) } func (j java) generateNormalTestCode(q *leetcode.QuestionData) (string, error) { @@ -64,6 +152,7 @@ func (j java) generateTestContent(q *leetcode.QuestionData) (string, error) { func (j java) generateCodeFile( q *leetcode.QuestionData, + packageName string, filename string, blocks []config.Block, modifiers []ModifierFunc, @@ -72,7 +161,11 @@ func (j java) generateCodeFile( FileOutput, error, ) { - codeHeader := "" + codeHeader := fmt.Sprintf( + `package %s; +`, + packageName, + ) testContent, err := j.generateTestContent(q) if err != nil { return FileOutput{}, err @@ -108,14 +201,14 @@ func (j java) generateCodeFile( func (j java) GeneratePaths(q *leetcode.QuestionData) (*GenerateResult, error) { filenameTmpl := getFilenameTemplate(q, j) - baseFilename, err := q.GetFormattedFilename(j.slug, filenameTmpl) + packageName, err := q.GetFormattedFilename(j.slug, filenameTmpl) if err != nil { return nil, err } genResult := &GenerateResult{ - SubDir: baseFilename, Question: q, Lang: j, + SubDir: filepath.Join(j.sourceDir(), packageName), } genResult.AddFile( FileOutput{ @@ -142,14 +235,14 @@ func (j java) GeneratePaths(q *leetcode.QuestionData) (*GenerateResult, error) { func (j java) Generate(q *leetcode.QuestionData) (*GenerateResult, error) { filenameTmpl := getFilenameTemplate(q, j) - baseFilename, err := q.GetFormattedFilename(j.slug, filenameTmpl) + packageName, err := q.GetFormattedFilename(j.slug, filenameTmpl) if err != nil { return nil, err } genResult := &GenerateResult{ Question: q, Lang: j, - SubDir: baseFilename, + SubDir: filepath.Join(j.sourceDir(), packageName), } separateDescriptionFile := separateDescriptionFile(j) @@ -158,7 +251,8 @@ func (j java) Generate(q *leetcode.QuestionData) (*GenerateResult, error) { if err != nil { return nil, err } - codeFile, err := j.generateCodeFile(q, "solution.java", blocks, modifiers, separateDescriptionFile) + fqPackageName := fmt.Sprintf("%s.%s", j.groupID(), packageName) + codeFile, err := j.generateCodeFile(q, fqPackageName, "solution.java", blocks, modifiers, separateDescriptionFile) if err != nil { return nil, err } diff --git a/leetcode/question.go b/leetcode/question.go index 5c2b1f42..8f130aee 100644 --- a/leetcode/question.go +++ b/leetcode/question.go @@ -562,6 +562,8 @@ func contestShortSlug(contestSlug string) string { return strings.Replace(contestSlug, "-contest-", "-", 1) } +var nonIdentifierRe = regexp.MustCompile(`[^a-zA-Z0-9_]`) + func (q *QuestionData) GetFormattedFilename(lang string, filenameTemplate string) (string, error) { id, slugValid := q.normalizeQuestionId() data := &filenameTemplateData{ @@ -595,7 +597,7 @@ func (q *QuestionData) GetFormattedFilename(lang string, filenameTemplate string return fmt.Sprintf("%0*s", n, s) }, "toUnderscore": func(s string) string { - return strings.ReplaceAll(s, "-", "_") + return nonIdentifierRe.ReplaceAllString(s, "_") }, "group": func(size int, s string) string { id, err := strconv.Atoi(s) diff --git a/testutils/java/.mvn/maven.config b/testutils/java/.mvn/maven.config new file mode 100644 index 00000000..0731b0c8 --- /dev/null +++ b/testutils/java/.mvn/maven.config @@ -0,0 +1,3 @@ +-Dmaven.plugin.validation=none +-DskipTests=true +-q diff --git a/testutils/java/.mvn/wrapper/maven-wrapper.properties b/testutils/java/.mvn/wrapper/maven-wrapper.properties index da382b75..a4c7cb97 100644 --- a/testutils/java/.mvn/wrapper/maven-wrapper.properties +++ b/testutils/java/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,5 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.2/apache-maven-3.9.2-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/testutils/java/src/main/java/io/github/j178/abc/A.java b/testutils/java/src/main/java/io/github/j178/abc/A.java new file mode 100644 index 00000000..830c2a5e --- /dev/null +++ b/testutils/java/src/main/java/io/github/j178/abc/A.java @@ -0,0 +1,4 @@ +package io.github.j178.abc; + +public class A { +}