git.sophuwu.com > gophuwu
added flag/arg parser
added parsers for int and float
added hashpass command as test
sophuwu sophie@sophuwu.com
Sat, 12 Jul 2025 00:43:54 +0200
commit

65e4a11e06aa7c275eed7bce403c360d2238b5b3

6 files changed, 495 insertions(+), 0 deletions(-)

jump to
A .gitignore

@@ -0,0 +1,2 @@

+.idea +build/
A flags/flags.go

@@ -0,0 +1,213 @@

+package flags + +import ( + "fmt" + "git.sophuwu.com/gophuwu/parsers" + "os" +) + +type flag struct { + Name string + Short string + HelpMsg string + Type string + Default interface{} + Value interface{} +} + +var flags = make(map[string]flag) +var shortFlags = make(map[byte]string) + +// NewFlag creates a new flag with the given name, help message, and default value. +// It returns an error if the flag name is invalid, if the flag already exists, +// or if the default value is of an unsupported type. +// Supported types for defaultValue are: string, int, bool, and float64. +// short must be a single character and unique across all flags. +// set short to an empty string if no short flag is needed. +func NewFlag(name, short, helpMsg string, defaultValue interface{}) error { + if len(name) == 0 { + return fmt.Errorf("flag name cannot be empty") + } + if _, exists := flags[name]; exists { + return fmt.Errorf("flag %s already exists", name) + } + if defaultValue == nil { + return fmt.Errorf("default value for flag %s cannot be nil", name) + } + var f flag + f.Name = name + f.HelpMsg = helpMsg + if len(short) > 1 { + return fmt.Errorf("short flag must be a single character") + } + if len(short) == 1 { + shrt := short[0] + if _, exists := shortFlags[shrt]; exists { + return fmt.Errorf("short flag %s already exists", short) + } + shortFlags[shrt] = name + f.Short = short + } + switch defaultValue.(type) { + case string: + f.Type = "string" + break + case int: + f.Type = "int" + break + case bool: + f.Type = "bool" + break + case float64: + f.Type = "float64" + break + default: + return fmt.Errorf("unsupported flag type for %s", name) + } + f.Default = defaultValue + flags[name] = f + return nil +} + +func getFlag(name, t string) (interface{}, error) { + f, exists := flags[name] + if !exists { + return nil, fmt.Errorf("flag %s does not exist", name) + } + if f.Type != t { + return nil, fmt.Errorf("flag %s is not of type bool", name) + } + if f.Value == nil { + return f.Default, nil + } + return f.Value, nil +} +func GetBoolFlag(name string) (bool, error) { + i, err := getFlag(name, "bool") + if err != nil { + return false, err + } + return i.(bool), nil +} +func GetIntFlag(name string) (int, error) { + i, err := getFlag(name, "int") + if err != nil { + return 0, err + } + return i.(int), nil +} +func GetStringFlag(name string) (string, error) { + i, err := getFlag(name, "string") + if err != nil { + return "", err + } + return i.(string), nil +} + +func GetFloat64Flag(name string) (float64, error) { + i, err := getFlag(name, "float64") + if err != nil { + return 0, err + } + return i.(float64), nil +} + +func ParseArgs() error { + if len(os.Args) < 2 { + return nil + } + var v string + var vv byte + var i, j int + var f flag + var ok bool + var err error + + shortFlags['h'] = "help" + + var args []string + for i = 1; i < len(os.Args); i++ { + v = os.Args[i] + if (len(v) > 2 && v[0] == '-' && v[1] != '-') || (len(v) > 1 && v[0] == '-') { + for j = 1; j < len(os.Args[i]); j++ { + vv = os.Args[i][j] + v, ok = shortFlags[vv] + if !ok { + return fmt.Errorf("unknown short flag: %c", vv) + } + args = append(args, "--"+v) + } + continue + } + args = append(args, v) + } + + for i = 0; i < len(args); i++ { + v = args[i] + if len(v) > 2 && v[0] == '-' && v[1] == '-' { + v = v[2:] + if v == "help" { + PrintHelp() + os.Exit(0) + } + f, ok = flags[v] + if !ok { + return fmt.Errorf("unknown flag: %s", v) + } + if f.Type == "bool" { + f.Value = !f.Default.(bool) + flags[f.Name] = f + continue + } + i++ + v = args[i] + if i >= len(args) { + return fmt.Errorf("flag %s requires a value", v) + } + switch f.Type { + case "string": + f.Value = v + break + case "int": + f.Value, err = parsers.ParseInt(v) + break + case "float64": + f.Value, err = parsers.ParseFloat(v) + break + default: + return fmt.Errorf("unsupported flag type for %s", f.Name) + } + if err != nil { + return fmt.Errorf("error parsing flag %s: %v", f.Name, err) + } + flags[f.Name] = f + } + } + return nil +} + +func PrintHelp() { + fmt.Printf("Usage: %s [options]\n", os.Args[0]) + fmt.Println("Options:") + fmt.Println(" -h --help\n\tShow this help message") + for _, f := range flags { + fmt.Printf(" ") + if len(f.Short) == 1 { + fmt.Printf("-%s, ", f.Short) + } else { + fmt.Printf(" ") + } + fmt.Printf("--%s ", f.Name) + if f.Type == "bool" { + fmt.Printf("\n") + } else { + fmt.Printf("<%s>\n", f.Type) + } + fmt.Printf("\t%s ", f.HelpMsg) + if f.Default != nil { + fmt.Printf("(default: %v)\n", f.Default) + } else { + fmt.Printf("\n") + } + } +}
A go.mod

@@ -0,0 +1,7 @@

+module git.sophuwu.com/gophuwu + +go 1.24.4 + +require golang.org/x/term v0.33.0 + +require golang.org/x/sys v0.34.0 // indirect
A go.sum

@@ -0,0 +1,4 @@

+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
A hashpw/hashpw.go

@@ -0,0 +1,161 @@

+package main + +import ( + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "git.sophuwu.com/gophuwu/flags" + "git.sophuwu.com/gophuwu/parsers" + "golang.org/x/term" + "hash" + "os" +) + +func fatal(err error, msg string) { + if err != nil { + println("fatal: " + msg + ": " + err.Error()) + os.Exit(1) + } +} + +func init() { + err := flags.NewFlag("algorithm", "a", "hash with sha1, sha256 or sha512", "sha256") + fatal(err, "could not add algorithm flag") + err = flags.NewFlag("encoder", "e", "encode with base64, base64url, hex or raw", "hex") + fatal(err, "could not add encoder flag") + err = flags.NewFlag("output", "o", "output file to write the hashed password to", "stdout") + fatal(err, "could not add output flag") + err = flags.NewFlag("newline", "n", "add a newline to the end of the output", false) + fatal(err, "could not add show flag") + err = flags.NewFlag("show", "s", "show input when typing", false) + fatal(err, "could not add show flag") + err = flags.ParseArgs() + fatal(err, "could not parse flags") +} + +func getEnc() func(src []byte) []byte { + encoder, err := flags.GetStringFlag("encoder") + fatal(err, "could not get encoder flag") + switch encoder { + case "base64": + return func(src []byte) []byte { + return []byte(base64.StdEncoding.EncodeToString(src)) + } + case "base64url": + return func(src []byte) []byte { + return []byte(base64.URLEncoding.EncodeToString(src)) + } + case "hex": + return func(src []byte) []byte { + return []byte(hex.EncodeToString(src)) + } + case "raw": + return func(src []byte) []byte { + return src + } + default: + fatal(errors.New("unknown encoder: "+encoder), "invalid encoder") + } + return nil +} + +func getHashFunc() hash.Hash { + algorithm, err := flags.GetStringFlag("algorithm") + fatal(err, "could not get algorithm flag") + switch algorithm { + case "sha1": + return sha1.New() + case "sha256": + return sha256.New() + case "sha512": + return sha512.New() + default: + fatal(errors.New("unknown algorithm: "+algorithm), "invalid algorithm") + } + return nil +} + +func getOutFunc() func(w []byte) error { + output, err := flags.GetStringFlag("output") + fatal(err, "could not get output flag") + if output == "stdout" { + return func(w []byte) error { + _, e := os.Stdout.Write(w) + return e + } + } + if output != "" { + _, err = os.Stat(output) + if !errors.Is(err, os.ErrNotExist) { + fatal(errors.New("output file already exists: "+output), "invalid output") + } + return func(w []byte) error { + f, e := os.Create(output) + if e != nil { + return e + } + _, e = f.Write(w) + f.Close() + return e + } + } + fatal(errors.New("invalid output: "+output), "invalid output") + return nil +} + +func main() { + var err error + + hashFunc := getHashFunc() + enc := getEnc() + out := getOutFunc() + + var line bool + line, err = flags.GetBoolFlag("newline") + fatal(err, "could not get newline flag") + var show bool + show, err = flags.GetBoolFlag("show") + fatal(err, "could not get show flag") + var password []byte + + fmt.Print("Enter password to hash: ") + if show { + password, err = parsers.ReadLine(os.Stdin) + fatal(err, "could not read password") + } else { + password, err = term.ReadPassword(0) + fmt.Println() + fatal(err, "could not read password") + fmt.Print("Enter password again: ") + var pas []byte + pas, err = term.ReadPassword(0) + fmt.Println() + fatal(err, "could not read password") + if string(pas) != string(password) { + fmt.Println("Passwords do not match, please try again.") + os.Exit(1) + } + } + if len(password) == 0 { + fatal(errors.New("password cannot be empty"), "invalid password") + } + + var n int + n, err = hashFunc.Write(password) + fatal(err, "could not write password to hash function") + if n != len(password) { + fatal(errors.New("could not write all bytes to hash function"), "invalid password") + } + encHash := hashFunc.Sum(nil) + encHash = enc(encHash) + if line { + encHash = append(encHash, '\n') + } + err = out(encHash) + fatal(err, "could not write output") + +}
A parsers/parsers.go

@@ -0,0 +1,108 @@

+package parsers + +import ( + "errors" + "io" + "runtime" +) + +func ParseInt(s string) (int, error) { + n := 0 + if len(s) == 0 { + return 0, errors.New("empty string cannot be parsed to int") + } + sign := 1 + if s[0] == '-' { + sign = -1 + s = s[1:] + } + for _, r := range s { + if r < '0' || r > '9' { + return n * sign, errors.New("invalid character in string, must be a digit") + } + n = n*10 + int(r-'0') + } + n *= sign + return n, nil +} + +func ParseFloat(s string) (float64, error) { + if len(s) == 0 { + return 0, errors.New("empty string cannot be parsed to float") + } + n := 0.0 + sign := 1.0 + if s[0] == '-' { + sign = -1.0 + s = s[1:] + } + decimalPoint := false + factor := 1.0 + for _, r := range s { + if r == '.' { + if decimalPoint { + return n * sign, errors.New("multiple decimal points in string") + } + decimalPoint = true + continue + } + if r < '0' || r > '9' { + return n * sign, errors.New("invalid character in string, must be a digit or decimal point") + } + if decimalPoint { + factor *= 10.0 + n += float64(r-'0') / factor + } else { + n = n*10 + float64(r-'0') + } + } + n *= sign + return n, nil +} + +func ParseBool(s string) (bool, error) { + switch s { + case "true", "1", "yes", "on": + return true, nil + case "false", "0", "no", "off": + return false, nil + default: + return false, errors.New("invalid boolean string, must be true/false, 1/0, yes/no, on/off") + } +} + +func ReadLine(reader io.Reader) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\b': + if len(ret) > 0 { + ret = ret[:len(ret)-1] + } + case '\n': + if runtime.GOOS != "windows" { + return ret, nil + } + // otherwise ignore \n + case '\r': + if runtime.GOOS == "windows" { + return ret, nil + } + // otherwise ignore \r + default: + ret = append(ret, buf[0]) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +}