sort : port over full code, fix flag value iteration skipping in parseArgs
This commit is contained in:
parent
d404b3d096
commit
ba46937c3c
2 changed files with 110 additions and 2 deletions
29
pipe/pipe.go
Normal file
29
pipe/pipe.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
83
sort/sort.go
83
sort/sort.go
|
|
@ -1,14 +1,20 @@
|
||||||
package sort
|
package sort
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/brtholomy/um/go/flags"
|
"github.com/brtholomy/um/go/flags"
|
||||||
|
"github.com/brtholomy/um/go/pipe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
Source flags.Flag[string]
|
Source flags.Flag[string]
|
||||||
Key flags.Flag[string]
|
Key flags.Flag[string]
|
||||||
|
Write flags.Flag[bool]
|
||||||
Help flags.Flag[bool]
|
Help flags.Flag[bool]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,20 +22,27 @@ func initOpts() options {
|
||||||
return options{
|
return options{
|
||||||
flags.Flag[string]{"--source", "-s", "", "path to source list. reads from stdin if omitted."},
|
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[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"},
|
flags.Flag[bool]{"--help", "-h", false, "show help"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseArgs(args []string) options {
|
func parseArgs(args []string) options {
|
||||||
opts := initOpts()
|
opts := initOpts()
|
||||||
for i, arg := range args {
|
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
arg := args[i]
|
||||||
switch {
|
switch {
|
||||||
case arg == opts.Source.Long || arg == opts.Source.Short:
|
case arg == opts.Source.Long || arg == opts.Source.Short:
|
||||||
flags.ValidValueOrExit(args, i)
|
flags.ValidValueOrExit(args, i)
|
||||||
opts.Source.Val = args[i+1]
|
opts.Source.Val = args[i+1]
|
||||||
|
i++
|
||||||
case arg == opts.Key.Long || arg == opts.Key.Short:
|
case arg == opts.Key.Long || arg == opts.Key.Short:
|
||||||
flags.ValidValueOrExit(args, i)
|
flags.ValidValueOrExit(args, i)
|
||||||
opts.Key.Val = args[i+1]
|
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:
|
case arg == opts.Help.Long || arg == opts.Help.Short:
|
||||||
flags.Help("sort", opts)
|
flags.Help("sort", opts)
|
||||||
default:
|
default:
|
||||||
|
|
@ -37,10 +50,76 @@ func parseArgs(args []string) options {
|
||||||
flags.Help("sort", opts)
|
flags.Help("sort", opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !opts.Key.IsSet() {
|
||||||
|
log.Printf("um sort: %s is required", opts.Key.Long)
|
||||||
|
flags.Help("sort", opts)
|
||||||
|
}
|
||||||
return 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) {
|
func Sort(args []string) {
|
||||||
opts := parseArgs(args)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue