package main import ( "flag" "fmt" "os" "strconv" "strings" ) type VERSION struct { Filename string Major, Minor, Patch, Revision uint64 HasMajor, HasMinor, HasPatch, HasRevision bool } func (v *VERSION) Flush() error { return os.WriteFile(v.Filename, []byte(v.String()), 0664) } func (v VERSION) String() string { var buffer strings.Builder if v.HasMajor || v.HasMinor || v.HasPatch || v.HasRevision { buffer.WriteString(fmt.Sprintf("%d", v.Major)) } if v.HasMinor || v.HasPatch || v.HasRevision { buffer.WriteString(fmt.Sprintf(".%d", v.Minor)) } if v.HasPatch || v.HasRevision { buffer.WriteString(fmt.Sprintf(".%d", v.Patch)) } if v.HasRevision { buffer.WriteString(fmt.Sprintf("-%d", v.Revision)) } return buffer.String() } func NewVersion(filename string) (VERSION, error) { f, err := os.ReadFile(filename) if err != nil { return VERSION{}, fmt.Errorf("error reading %s: %w", filename, err) } fields := strings.FieldsFunc(string(f), func(r rune) bool { return r == '.' || r == '-' }) if len(fields) > 4 { return VERSION{}, fmt.Errorf("invalid VERSION file format - %d fields (expected 0-4)", len(fields)) } results := VERSION{ Filename: filename, } if len(fields) > 0 { results.Major, err = strconv.ParseUint(fields[0], 10, 64) if err != nil { return VERSION{}, fmt.Errorf("error parsing major version: %w", err) } results.HasMajor = true } if len(fields) > 1 { results.Minor, err = strconv.ParseUint(fields[1], 10, 64) if err != nil { return VERSION{}, fmt.Errorf("error parsing minor version: %w", err) } results.HasMinor = true } if len(fields) > 2 { results.Patch, err = strconv.ParseUint(fields[2], 10, 64) if err != nil { return VERSION{}, fmt.Errorf("error parsing patch version: %w", err) } results.HasPatch = true } if len(fields) > 3 { results.Revision, err = strconv.ParseUint(fields[3], 10, 64) if err != nil { return VERSION{}, fmt.Errorf("error parsing revision: %w", err) } results.HasRevision = true } return results, nil } func IncrementVersion(version VERSION, major, minor, patch, revision bool) VERSION { if major { version.Major++ version.HasMajor = true } if minor { version.Minor++ version.HasMinor = true } if patch { version.Patch++ version.HasPatch = true } if revision { version.Revision++ version.HasRevision = true } return version } func main() { flag.Usage = func() { fmt.Println("usage: increment_version ") fmt.Println(" increment_version ") fmt.Println("increment_version increments the version of a given VERSION file.") fmt.Println("It accepts the following formats:") fmt.Println("\t..-") fmt.Println("\t..") fmt.Println("\t.") fmt.Println("\t") fmt.Println("the following flags are used to determine which fields to increment.") fmt.Println("using a flag without a corresponding value in the VERSION file will result") fmt.Println("in that field, along with all the necessary fields to the left being") fmt.Println("created inside that file.") } var major, minor, patch, revision, dry bool flag.BoolVar(&major, "major", false, "increment major version") flag.BoolVar(&minor, "minor", false, "increment minor version") flag.BoolVar(&patch, "patch", false, "increment patch version") flag.BoolVar(&revision, "revision", false, "increment revision") flag.BoolVar(&dry, "dry", true, "do not change the file (just print the new version)") flag.Parse() args := flag.Args() if len(args) == 0 { fmt.Println("requires path to VERSION file") flag.Usage() os.Exit(1) } filename := strings.Join(args, " ") oldversion, err := NewVersion(filename) if err != nil { fmt.Printf("error loading version file %s: %v\n", filename, err) os.Exit(1) } newversion := IncrementVersion(oldversion, major, minor, patch, revision) fmt.Printf("'%s' => '%s'\n", oldversion, newversion) if !dry { err = newversion.Flush() if err != nil { fmt.Printf("error writing updated VERSION to %s: %v\n", filename, err) } } }