From ba46937c3cf0a6c2fc602f6d52532e21982a885c Mon Sep 17 00:00:00 2001 From: bth Date: Mon, 19 Jan 2026 11:20:37 -0800 Subject: [PATCH] sort : port over full code, fix flag value iteration skipping in parseArgs --- pipe/pipe.go | 29 ++++++++++++++++++ sort/sort.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 pipe/pipe.go diff --git a/pipe/pipe.go b/pipe/pipe.go new file mode 100644 index 0000000..2629823 --- /dev/null +++ b/pipe/pipe.go @@ -0,0 +1,29 @@ +package pipe + +import ( + "errors" + "io" + "log" + "os" + "strings" +) + +const Newline string = "\n" + +func isStdinLoaded() bool { + stat, _ := os.Stdin.Stat() + return (stat.Mode() & os.ModeCharDevice) == 0 +} + +func GetStdin() ([]string, error) { + if !isStdinLoaded() { + return nil, errors.New("stdin not loaded") + } + data, err := io.ReadAll(os.Stdin) + if err != nil { + log.Fatal(err) + } + // TODO: there's got to be a better way: + s, _ := strings.CutSuffix(string(data), Newline) + return strings.Split(s, Newline), nil +} diff --git a/sort/sort.go b/sort/sort.go index 08513f2..7d21ec7 100644 --- a/sort/sort.go +++ b/sort/sort.go @@ -1,14 +1,20 @@ package sort import ( + "fmt" "log" + "os" + "slices" + "strings" "github.com/brtholomy/um/go/flags" + "github.com/brtholomy/um/go/pipe" ) type options struct { Source flags.Flag[string] Key flags.Flag[string] + Write flags.Flag[bool] Help flags.Flag[bool] } @@ -16,20 +22,27 @@ func initOpts() options { return options{ flags.Flag[string]{"--source", "-s", "", "path to source list. reads from stdin if omitted."}, flags.Flag[string]{"--key", "-k", "", "path to sort key"}, + flags.Flag[bool]{"--write", "-w", false, "write sorted list back to --key file"}, flags.Flag[bool]{"--help", "-h", false, "show help"}, } } func parseArgs(args []string) options { opts := initOpts() - for i, arg := range args { + + for i := 0; i < len(args); i++ { + arg := args[i] switch { case arg == opts.Source.Long || arg == opts.Source.Short: flags.ValidValueOrExit(args, i) opts.Source.Val = args[i+1] + i++ case arg == opts.Key.Long || arg == opts.Key.Short: flags.ValidValueOrExit(args, i) opts.Key.Val = args[i+1] + i++ + case arg == opts.Write.Long || arg == opts.Write.Short: + opts.Write.Val = true case arg == opts.Help.Long || arg == opts.Help.Short: flags.Help("sort", opts) default: @@ -37,10 +50,76 @@ func parseArgs(args []string) options { flags.Help("sort", opts) } } + if !opts.Key.IsSet() { + log.Printf("um sort: %s is required", opts.Key.Long) + flags.Help("sort", opts) + } return opts } +// reads files from stdin if present, otherwise from the filename: +func fileListSplitMaybeStdin(f string) []string { + filelist, err := pipe.GetStdin() + if err != nil { + filelist = fileListSplit(f) + } + return filelist +} + +// opens the given filename and splits into lines: +func fileListSplit(f string) []string { + dat, err := os.ReadFile(f) + if err != nil { + // if this fails all is lost. just exit. + log.Fatal(fmt.Errorf("um sort: error opening file: %s\n%w", f, err)) + } + return strings.Split(string(dat), pipe.Newline) +} + +// record the order of the given filelist. +func kMap(kslice []string) map[string]int { + m := make(map[string]int, 0) + for i, l := range kslice { + m[l] = i + } + return m +} + +// write out the provided source slice while respecting the order provided by kmap +func sort(sslice []string, kmap map[string]int) string { + oslice := make([]string, max(len(sslice), len(kmap))+1) + for _, l := range sslice { + // lines not represented in the key get appended to the end: + if _, ok := kmap[l]; !ok { + // we don't care that this might leave empty strings between: + oslice = append(oslice, l) + continue + } + // NOTE: raison d'etre of this whole thang: + // if present in the key, respect the order: + oslice[kmap[l]] = l + } + // delete empties and add a final newline + oslice = slices.DeleteFunc(oslice, func(e string) bool { return e == "" }) + return strings.Join(oslice, pipe.Newline) + pipe.Newline +} + +func Write(file string, content string) { + if err := os.WriteFile(file, []byte(content), 0664); err != nil { + log.Fatal(err) + } +} + func Sort(args []string) { opts := parseArgs(args) - log.Println(opts) + + sslice := fileListSplitMaybeStdin(opts.Source.Val) + kslice := fileListSplit(opts.Key.Val) + kmap := kMap(kslice) + out := sort(sslice, kmap) + if opts.Write.Val { + Write(opts.Key.Val, out) + } else { + fmt.Print(out) + } }