Repository: j178/leetgo (language: python)

Issue:
feat: version control of `LC_IO.h`



Discussion / hints:

Fix patch:
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index e48664da..96f2446e 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -18,9 +18,9 @@ builds:
     ldflags:
       - -s
       - -w
-      - -X github.com/j178/leetgo/cmd.version={{.Version}}
-      - -X github.com/j178/leetgo/cmd.commit={{.Commit}}
-      - -X github.com/j178/leetgo/cmd.date={{.Date}}
+      - -X github.com/j178/leetgo/constants.Version={{.Version}}
+      - -X github.com/j178/leetgo/constants.Commit={{.Commit}}
+      - -X github.com/j178/leetgo/constants.BuildDate={{.Date}}
 
 archives:
   - format: tar.gz
diff --git a/cmd/init.go b/cmd/init.go
index 1e2be57b..ced2d109 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -9,6 +9,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/j178/leetgo/config"
+	"github.com/j178/leetgo/constants"
 	"github.com/j178/leetgo/leetcode"
 	"github.com/j178/leetgo/utils"
 )
@@ -91,7 +92,7 @@ func createConfigFiles(dir string) error {
 		log.Info("global config file created", "file", globalFile)
 	}
 
-	projectFile := filepath.Join(dir, config.ProjectConfigFilename)
+	projectFile := filepath.Join(dir, constants.ProjectConfigFilename)
 	f, err := os.Create(projectFile)
 	if err != nil {
 		return err
diff --git a/cmd/root.go b/cmd/root.go
index e59b3bfe..29a4eb26 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -12,21 +12,16 @@ import (
 	"github.com/spf13/viper"
 
 	"github.com/j178/leetgo/config"
-)
-
-var (
-	version = "0.0.1"
-	commit  = "HEAD"
-	date    = "unknown"
+	"github.com/j178/leetgo/constants"
 )
 
 func buildVersion() string {
-	result := version
-	if commit != "" {
-		result = fmt.Sprintf("%s\ncommit: %s", result, commit)
+	result := constants.Version
+	if constants.Commit != "" {
+		result = fmt.Sprintf("%s\ncommit: %s", result, constants.Commit)
 	}
-	if date != "" {
-		result = fmt.Sprintf("%s\nbuilt at: %s", result, date)
+	if constants.BuildDate != "" {
+		result = fmt.Sprintf("%s\nbuilt at: %s", result, constants.BuildDate)
 	}
 	result = fmt.Sprintf("%s\ngoos: %s\ngoarch: %s", result, runtime.GOOS, runtime.GOARCH)
 	if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" {
@@ -36,10 +31,10 @@ func buildVersion() string {
 }
 
 var rootCmd = &cobra.Command{
-	Use:           config.CmdName,
+	Use:           constants.CmdName,
 	Short:         "Leetcode",
 	Long:          "Leetcode friend for geek.",
-	Version:       buildVersion() + "\n\n" + config.ProjectURL,
+	Version:       buildVersion() + "\n\n" + constants.ProjectURL,
 	SilenceErrors: true,
 	SilenceUsage:  true,
 	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
diff --git a/config/config.go b/config/config.go
index e38e9846..e4847162 100644
--- a/config/config.go
+++ b/config/config.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/charmbracelet/log"
+	"github.com/j178/leetgo/constants"
 	"github.com/mitchellh/go-homedir"
 	"github.com/spf13/viper"
 	"gopkg.in/yaml.v3"
@@ -15,18 +16,6 @@ import (
 	"github.com/j178/leetgo/utils"
 )
 
-const (
-	CmdName               = "leetgo"
-	globalConfigFile      = "config.yaml"
-	ProjectConfigFilename = "leetgo.yaml"
-	questionCacheBaseName = "leetcode-questions"
-	stateFilename         = "state.json"
-	CodeBeginMarker       = "@lc code=begin"
-	CodeEndMarker         = "@lc code=end"
-	GoTestUtilsModPath    = "github.com/j178/leetgo/testutils/go"
-	ProjectURL            = "https://github.com/j178/leetgo"
-)
-
 var (
 	globalCfg *Config
 	Debug     = os.Getenv("DEBUG") == "1"
@@ -125,7 +114,7 @@ type LeetCodeConfig struct {
 func (c *Config) ConfigDir() string {
 	if c.dir == "" {
 		home, _ := homedir.Dir()
-		c.dir = filepath.Join(home, ".config", CmdName)
+		c.dir = filepath.Join(home, ".config", constants.CmdName)
 	}
 	return c.dir
 }
@@ -135,11 +124,11 @@ func (c *Config) CacheDir() string {
 }
 
 func (c *Config) TempDir() string {
-	return filepath.Join(os.TempDir(), CmdName)
+	return filepath.Join(os.TempDir(), constants.CmdName)
 }
 
 func (c *Config) GlobalConfigFile() string {
-	return filepath.Join(c.ConfigDir(), globalConfigFile)
+	return filepath.Join(c.ConfigDir(), constants.GlobalConfigFilename)
 }
 
 func (c *Config) ProjectRoot() string {
@@ -147,7 +136,7 @@ func (c *Config) ProjectRoot() string {
 		dir, _ := os.Getwd()
 		c.projectRoot = dir
 		for {
-			if utils.IsExist(filepath.Join(dir, ProjectConfigFilename)) {
+			if utils.IsExist(filepath.Join(dir, constants.ProjectConfigFilename)) {
 				c.projectRoot = dir
 				break
 			}
@@ -163,15 +152,15 @@ func (c *Config) ProjectRoot() string {
 }
 
 func (c *Config) ProjectConfigFile() string {
-	return filepath.Join(c.ProjectRoot(), ProjectConfigFilename)
+	return filepath.Join(c.ProjectRoot(), constants.ProjectConfigFilename)
 }
 
 func (c *Config) StateFile() string {
-	return filepath.Join(c.CacheDir(), stateFilename)
+	return filepath.Join(c.CacheDir(), constants.StateFilename)
 }
 
 func (c *Config) QuestionCacheFile(ext string) string {
-	return filepath.Join(c.CacheDir(), questionCacheBaseName+ext)
+	return filepath.Join(c.CacheDir(), constants.QuestionCacheBaseName+ext)
 }
 
 func (c *Config) Write(w io.Writer, withComments bool) error {
@@ -338,7 +327,7 @@ func Load(init bool) error {
 		if err != nil {
 			if os.IsNotExist(err) {
 				log.Warn(
-					fmt.Sprintf("%s not found, use global config only", ProjectConfigFilename),
+					fmt.Sprintf("%s not found, use global config only", constants.ProjectConfigFilename),
 					"file",
 					cfg.GlobalConfigFile(),
 				)
diff --git a/config/encrypt.go b/config/encrypt.go
index 97c30c2a..1e149936 100644
--- a/config/encrypt.go
+++ b/config/encrypt.go
@@ -8,12 +8,14 @@ import (
 
 	vault "github.com/sosedoff/ansible-vault-go"
 	"github.com/zalando/go-keyring"
+
+	"github.com/j178/leetgo/constants"
 )
 
 const (
 	originVaultHeader = "$ANSIBLE_VAULT;1.1;AES256"
 	vaultHeader       = "$LEETGO_VAULT;1.1;AES256"
-	serviceName       = CmdName
+	serviceName       = constants.CmdName
 	keyName           = "encryption_key"
 )
 
diff --git a/constants/constants.go b/constants/constants.go
new file mode 100644
index 00000000..fcd3e548
--- /dev/null
+++ b/constants/constants.go
@@ -0,0 +1,19 @@
+package constants
+
+var (
+	Version   = "0.0.1"
+	Commit    = "HEAD"
+	BuildDate = "unknown"
+)
+
+const (
+	CmdName               = "leetgo"
+	GlobalConfigFilename  = "config.yaml"
+	ProjectConfigFilename = "leetgo.yaml"
+	QuestionCacheBaseName = "leetcode-questions"
+	StateFilename         = "state.json"
+	CodeBeginMarker       = "@lc code=begin"
+	CodeEndMarker         = "@lc code=end"
+	GoTestUtilsModPath    = "github.com/j178/leetgo/testutils/go"
+	ProjectURL            = "https://github.com/j178/leetgo"
+)
diff --git a/editor/editor.go b/editor/editor.go
index 0e475011..7bdb6ef3 100644
--- a/editor/editor.go
+++ b/editor/editor.go
@@ -8,6 +8,7 @@ import (
 	"github.com/charmbracelet/log"
 
 	"github.com/j178/leetgo/config"
+	"github.com/j178/leetgo/constants"
 	"github.com/j178/leetgo/lang"
 )
 
@@ -26,13 +27,13 @@ var editors = map[string]Opener{
 	"vim": &commonMultiEditor{
 		commonEditor{
 			command: "vim",
-			args:    []string{"-p", fmt.Sprintf("+/%s", config.CodeBeginMarker)},
+			args:    []string{"-p", fmt.Sprintf("+/%s", constants.CodeBeginMarker)},
 		},
 	},
 	"neovim": &commonMultiEditor{
 		commonEditor{
 			command: "nvim",
-			args:    []string{"-p", fmt.Sprintf("+/%s", config.CodeBeginMarker)},
+			args:    []string{"-p", fmt.Sprintf("+/%s", constants.CodeBeginMarker)},
 		},
 	},
 	"vscode": &commonMultiEditor{commonEditor{command: "code"}},
diff --git a/lang/base.go b/lang/base.go
index dd745ab5..bca0d43a 100644
--- a/lang/base.go
+++ b/lang/base.go
@@ -14,6 +14,7 @@ import (
 	"github.com/spf13/viper"
 
 	"github.com/j178/leetgo/config"
+	"github.com/j178/leetgo/constants"
 	"github.com/j178/leetgo/leetcode"
 	"github.com/j178/leetgo/utils"
 )
@@ -390,8 +391,8 @@ func (l baseLang) generateCodeContent(
 		LineComment:             l.lineComment,
 		BlockCommentStart:       l.blockCommentStart,
 		BlockCommentEnd:         l.blockCommentEnd,
-		CodeBeginMarker:         config.CodeBeginMarker,
-		CodeEndMarker:           config.CodeEndMarker,
+		CodeBeginMarker:         constants.CodeBeginMarker,
+		CodeEndMarker:           constants.CodeEndMarker,
 		Code:                    code,
 		SeparateDescriptionFile: separateDescriptionFile,
 		NeedsDefinition:         needsDefinition(code),
diff --git a/lang/cpp.go b/lang/cpp.go
index 557f60dc..9b54eb03 100644
--- a/lang/cpp.go
+++ b/lang/cpp.go
@@ -10,6 +10,7 @@ import (
 	"github.com/charmbracelet/log"
 
 	"github.com/j178/leetgo/config"
+	"github.com/j178/leetgo/constants"
 	"github.com/j178/leetgo/leetcode"
 	cppUtils "github.com/j178/leetgo/testutils/cpp"
 	"github.com/j178/leetgo/utils"
@@ -21,7 +22,8 @@ type cpp struct {
 
 func (c cpp) Initialize(outDir string) error {
 	headerPath := filepath.Join(outDir, cppUtils.HeaderName)
-	if _, err := tryWrite(headerPath, cppUtils.HeaderContent); err != nil {
+	err := utils.WriteFile(headerPath, cppUtils.HeaderContent)
+	if err != nil {
 		return err
 	}
 	return nil
@@ -29,7 +31,19 @@ func (c cpp) Initialize(outDir string) error {
 
 func (c cpp) HasInitialized(outDir string) (bool, error) {
 	headerPath := filepath.Join(outDir, cppUtils.HeaderName)
-	return utils.IsExist(headerPath), nil
+	if !utils.IsExist(headerPath) {
+		return false, nil
+	}
+
+	version, err := ReadVersion(headerPath)
+	if err != nil {
+		return false, err
+	}
+	currVersion := constants.Version
+	if version != currVersion {
+		return false, nil
+	}
+	return true, nil
 }
 
 var cppTypes = map[string]string{
diff --git a/lang/gen.go b/lang/gen.go
index c7c64428..77548dd8 100644
--- a/lang/gen.go
+++ b/lang/gen.go
@@ -10,6 +10,7 @@ import (
 	"github.com/spf13/viper"
 
 	"github.com/j178/leetgo/config"
+	"github.com/j178/leetgo/constants"
 	"github.com/j178/leetgo/leetcode"
 	"github.com/j178/leetgo/utils"
 )
@@ -152,14 +153,11 @@ func tryWrite(file string, content string) (bool, error) {
 		return false, nil
 	}
 
-	err := utils.CreateIfNotExists(file, false)
-	if err != nil {
-		return false, err
-	}
-	err = os.WriteFile(file, utils.StringToBytes(content), 0o644)
+	err := utils.WriteFile(file, []byte(content))
 	if err != nil {
 		return false, err
 	}
+
 	log.Info("generated", "file", relPath)
 	return true, nil
 }
@@ -199,11 +197,11 @@ func GetSolutionCode(q *leetcode.QuestionData) (string, error) {
 	var codeLinesToKeep []string
 	inCode := false
 	for _, line := range codeLines {
-		if !inCode && strings.Contains(line, config.CodeBeginMarker) {
+		if !inCode && strings.Contains(line, constants.CodeBeginMarker) {
 			inCode = true
 			continue
 		}
-		if inCode && strings.Contains(line, config.CodeEndMarker) {
+		if inCode && strings.Contains(line, constants.CodeEndMarker) {
 			break
 		}
 		if inCode {
@@ -241,11 +239,11 @@ func UpdateSolutionCode(q *leetcode.QuestionData, newCode string) error {
 	var newLines []string
 	skip := false
 	for _, line := range lines {
-		if strings.Contains(line, config.CodeBeginMarker) {
+		if strings.Contains(line, constants.CodeBeginMarker) {
 			newLines = append(newLines, line)
 			newLines = append(newLines, newCode)
 			skip = true
-		} else if strings.Contains(line, config.CodeEndMarker) {
+		} else if strings.Contains(line, constants.CodeEndMarker) {
 			newLines = append(newLines, line)
 			skip = false
 		} else if !skip {
diff --git a/lang/go.go b/lang/go.go
index 72df4be7..691c7121 100644
--- a/lang/go.go
+++ b/lang/go.go
@@ -11,6 +11,7 @@ import (
 	"github.com/charmbracelet/log"
 
 	"github.com/j178/leetgo/config"
+	"github.com/j178/leetgo/constants"
 	"github.com/j178/leetgo/leetcode"
 )
 
@@ -100,7 +101,7 @@ func addMod(code string, q *leetcode.QuestionData) string {
 }
 
 func (g golang) HasInitialized(outDir string) (bool, error) {
-	cmd := exec.Command("go", "list", "-m", "-json", config.GoTestUtilsModPath)
+	cmd := exec.Command("go", "list", "-m", "-json", constants.GoTestUtilsModPath)
 	cmd.Dir = outDir
 	output, err := cmd.CombinedOutput()
 	if err != nil {
@@ -127,7 +128,7 @@ func (g golang) Initialize(outDir string) error {
 		return err
 	}
 
-	cmd = exec.Command("go", "get", config.GoTestUtilsModPath)
+	cmd = exec.Command("go", "get", constants.GoTestUtilsModPath)
 	cmd.Dir = outDir
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
@@ -335,7 +336,7 @@ import (
 	"os"
 
 	. "%s"
-)`, config.GoTestUtilsModPath,
+)`, constants.GoTestUtilsModPath,
 	)
 	testContent, err := g.generateTestContent(q)
 	if err != nil {
diff --git a/lang/version.go b/lang/version.go
new file mode 100644
index 00000000..e655d0d9
--- /dev/null
+++ b/lang/version.go
@@ -0,0 +1,25 @@
+package lang
+
+import (
+	"bufio"
+	"os"
+	"strings"
+)
+
+func ReadVersion(file string) (string, error) {
+	headerFile, err := os.Open(file)
+	if err != nil {
+		return "", err
+	}
+	defer headerFile.Close()
+
+	scanner := bufio.NewScanner(headerFile)
+	if scanner.Scan() {
+		versionLine := scanner.Text()
+		version := versionLine[strings.Index(versionLine, "version: ")+len("version: "):]
+		version = strings.TrimSpace(version)
+		return version, nil
+	}
+
+	return "", scanner.Err()
+}
diff --git a/utils/file.go b/utils/file.go
index 13ad0772..e28320ad 100644
--- a/utils/file.go
+++ b/utils/file.go
@@ -42,6 +42,18 @@ func CreateIfNotExists(path string, isDir bool) error {
 	return nil
 }
 
+func WriteFile(file string, content []byte) error {
+	err := CreateIfNotExists(file, false)
+	if err != nil {
+		return err
+	}
+	err = os.WriteFile(file, content, 0o644)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func RemoveIfExist(path string) error {
 	err := os.Remove(path)
 	if os.IsNotExist(err) {


Test patch:
diff --git a/testutils/cpp/header.go b/testutils/cpp/header.go
index 272f8c93..ca73609e 100644
--- a/testutils/cpp/header.go
+++ b/testutils/cpp/header.go
@@ -1,8 +1,17 @@
 package cpp
 
-import _ "embed"
+import (
+	_ "embed"
+	"fmt"
 
-//go:embed LC_IO.h
-var HeaderContent string
+	"github.com/j178/leetgo/constants"
+)
 
 const HeaderName = "LC_IO.h"
+
+//go:embed LC_IO.h
+var HeaderContent []byte
+
+func init() {
+	HeaderContent = fmt.Appendf(nil, "// version: %s\n%s", constants.Version, HeaderContent)
+}