From 193727dc0f48b75dedadf7616694d98e3145dd55 Mon Sep 17 00:00:00 2001 From: rwxrob Date: Fri, 25 Feb 2022 21:46:36 -0500 Subject: [PATCH] Factor out functional (fn), sets, loops --- cmd.go | 6 +- cmd/help.go | 7 ++- comp/file.go | 21 +++++-- comp/standard.go | 7 ++- comp/standard_test.go | 4 +- filter/filter.go => filt/filt.go | 2 +- filter/filter_test.go => filt/filt_test.go | 6 +- fn/fn.go | 16 ++++++ fn/fn_test.go | 65 ++++++++++++++++++++++ generic/generic.go | 27 +++++++++ loop/loop.go | 22 ++++++++ loop/loop_test.go | 17 ++++++ maps/maps.go | 28 ++++++++++ maps/maps_test.go | 39 +++++++++++++ set/set.go | 24 ++++++++ set/set_test.go | 16 ++++++ util/files.go | 4 +- 17 files changed, 289 insertions(+), 22 deletions(-) rename filter/filter.go => filt/filt.go (95%) rename filter/filter_test.go => filt/filt_test.go (70%) create mode 100644 fn/fn.go create mode 100644 fn/fn_test.go create mode 100644 generic/generic.go create mode 100644 loop/loop.go create mode 100644 loop/loop_test.go create mode 100644 maps/maps.go create mode 100644 maps/maps_test.go create mode 100644 set/set.go create mode 100644 set/set_test.go diff --git a/cmd.go b/cmd.go index e399e52..b7e981c 100644 --- a/cmd.go +++ b/cmd.go @@ -8,7 +8,7 @@ import ( "os" "github.com/rwxrob/bonzai/comp" - "github.com/rwxrob/bonzai/filter" + "github.com/rwxrob/bonzai/loop" ) // Cmd is a struct the easier to use and read when creating @@ -79,10 +79,10 @@ func (x *Cmd) Run() { if cmd.Completer == nil { list := comp.Standard(cmd, args...) - filter.Println(list) + loop.Println(list) Exit() } - filter.Println(cmd.Completer(cmd, args...)) + loop.Println(cmd.Completer(cmd, args...)) Exit() } diff --git a/cmd/help.go b/cmd/help.go index b7d03d0..030eb3e 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -9,7 +9,8 @@ import ( "github.com/rwxrob/bonzai" "github.com/rwxrob/bonzai/check" "github.com/rwxrob/bonzai/comp" - "github.com/rwxrob/bonzai/filter" + "github.com/rwxrob/bonzai/filt" + "github.com/rwxrob/bonzai/maps" ) // Help provides help documentation for the caller allowing the specific @@ -47,7 +48,7 @@ func helpCompleter(x comp.Command, args ...string) []string { if !check.IsNil(caller) { other := caller.GetOther() if other != nil { - list = append(list, filter.Keys(other)...) + list = append(list, maps.Keys(other)...) } } @@ -55,5 +56,5 @@ func helpCompleter(x comp.Command, args ...string) []string { return list } - return filter.HasPrefix(list, args[0]) + return filt.HasPrefix(list, args[0]) } diff --git a/comp/file.go b/comp/file.go index 6fb9485..ab4d3ca 100644 --- a/comp/file.go +++ b/comp/file.go @@ -4,7 +4,10 @@ package comp import ( - "github.com/rwxrob/bonzai/filter" + "strings" + + "github.com/rwxrob/bonzai/filt" + "github.com/rwxrob/bonzai/maps" "github.com/rwxrob/bonzai/util" ) @@ -12,15 +15,23 @@ import ( // passed. If nothing is passed assumes the current working directory. func File(x Command, args ...string) []string { match := "" - dir := "." + dir := "" if len(args) > 0 { - match = args[0] + // FIXME if there is an unescaped "/" at all, truncate the directory + // and keep the rest to add on later for match + if strings.HasSuffix(args[0], "/") { + dir = args[0] + match = "" + } else { + match = args[0] + } } list := []string{} - list = append(list, util.Files(dir)...) - list = filter.HasPrefix(list, match) + list = append(list, maps.Prefix(util.Files(dir), dir)...) + list = filt.HasPrefix(list, match) + list = maps.CleanPaths(list) return list } diff --git a/comp/standard.go b/comp/standard.go index 0de6621..26bc221 100644 --- a/comp/standard.go +++ b/comp/standard.go @@ -4,7 +4,8 @@ package comp import ( - "github.com/rwxrob/bonzai/filter" + "github.com/rwxrob/bonzai/filt" + "github.com/rwxrob/bonzai/set" ) // Standard completion is resolved as follows: @@ -36,11 +37,11 @@ func Standard(x Command, args ...string) []string { list := []string{} list = append(list, x.GetCommands()...) list = append(list, x.GetParams()...) - list = filter.Minus(list, x.GetHidden()) + list = set.Minus(list, x.GetHidden()) if len(args) == 0 { return list } - return filter.HasPrefix(list, args[0]) + return filt.HasPrefix(list, args[0]) } diff --git a/comp/standard_test.go b/comp/standard_test.go index cc0b439..ea23fab 100644 --- a/comp/standard_test.go +++ b/comp/standard_test.go @@ -8,7 +8,7 @@ import ( "github.com/rwxrob/bonzai" "github.com/rwxrob/bonzai/comp" - "github.com/rwxrob/bonzai/filter" + "github.com/rwxrob/bonzai/filt" ) func ExampleStandard() { @@ -39,7 +39,7 @@ func ExampleStandard() { if len(args) == 0 { return list } - return filter.HasPrefix(list, args[0]) + return filt.HasPrefix(list, args[0]) } fmt.Println(comp.Standard(foo, `t`)) diff --git a/filter/filter.go b/filt/filt.go similarity index 95% rename from filter/filter.go rename to filt/filt.go index d89b325..f9fce90 100644 --- a/filter/filter.go +++ b/filt/filt.go @@ -1,4 +1,4 @@ -package filter +package filt import ( "strings" diff --git a/filter/filter_test.go b/filt/filt_test.go similarity index 70% rename from filter/filter_test.go rename to filt/filt_test.go index 5187aab..620af0f 100644 --- a/filter/filter_test.go +++ b/filt/filt_test.go @@ -1,19 +1,19 @@ // Copyright 2022 Robert S. Muhlestein. // SPDX-License-Identifier: Apache-2.0 -package filter_test +package filt_test import ( "fmt" - "github.com/rwxrob/bonzai/filter" + "github.com/rwxrob/bonzai/filt" ) func ExampleHasPrefix() { set := []string{ "one", "two", "three", "four", "five", "six", "seven", } - fmt.Println(filter.HasPrefix(set, "t")) + fmt.Println(filt.HasPrefix(set, "t")) // Output: // [two three] } diff --git a/fn/fn.go b/fn/fn.go new file mode 100644 index 0000000..3cea2cd --- /dev/null +++ b/fn/fn.go @@ -0,0 +1,16 @@ +// Copyright 2022 Robert S. Muhlestein. +// SPDX-License-Identifier: Apache-2.0 + +package fn + +// Map executes an operator function provided on each item in the +// slice returning a new slice. If error handling is needed it should be +// handled within an enclosure within the function. This keeps +// signatures simple and functional. +func Map[I any, O any](slice []I, f func(in I) O) []O { + list := []O{} + for _, i := range slice { + list = append(list, f(i)) + } + return list +} diff --git a/fn/fn_test.go b/fn/fn_test.go new file mode 100644 index 0000000..8a2423b --- /dev/null +++ b/fn/fn_test.go @@ -0,0 +1,65 @@ +// Copyright 2022 Robert S. Muhlestein. +// SPDX-License-Identifier: Apache-2.0 + +package fn_test + +import ( + "fmt" + + "github.com/rwxrob/bonzai/fn" +) + +func ExampleHasPrefix() { + set := []string{ + "one", "two", "three", "four", "five", "six", "seven", + } + fmt.Println(fn.HasPrefix(set, "t")) + // Output: + // [two three] +} + +func ExamplePrintln() { + set := []string{"doe", "ray", "mi"} + filter.Println(set) + bools := []bool{false, true, true} + filter.Println(bools) + // Output: + // doe + // ray + // mi + // false + // true + // true +} + +func ExampleKeys() { + m1 := map[string]int{"two": 2, "three": 3, "one": 1} + m2 := map[string]string{"two": "two", "three": "three", "one": "one"} + fmt.Println(filter.Keys(m1)) + fmt.Println(filter.Keys(m2)) + // Output: + // [one three two] + // [one three two] +} + +func ExamplePrefix() { + fmt.Println(filter.Prefix([]string{"foo", "bar"}, "my")) + // Output: + // [myfoo mybar] +} + +func ExampleCleanPaths() { + paths := []string{ + ``, + `.`, + `./`, + `./thing`, + `/sub/../../thing`, + } + filter.Println(filter.CleanPaths(paths)) + // Output: + // . + // . + // . + // /thing +} diff --git a/generic/generic.go b/generic/generic.go new file mode 100644 index 0000000..4470440 --- /dev/null +++ b/generic/generic.go @@ -0,0 +1,27 @@ +// Copyright 2022 Robert S. Muhlestein. +// SPDX-License-Identifier: Apache-2.0 + +package generic + +// Number combines the primitives generally considered numbers by JSON +// and other high-level structure data representations. +type Number interface { + int | int64 | int32 | int16 | int8 | + uint64 | uint32 | uint16 | uint8 | + float64 | float32 +} + +// Text combines byte slice and string. +type Text interface { + []byte | string +} + +// Sharable are the types that have representations in JSON, YAML, TOML +// and other high-level structured data representations. +type Sharable interface { + int | int64 | int32 | int16 | int8 | + uint64 | uint32 | uint16 | uint8 | + float64 | float32 | + []byte | string | + bool +} diff --git a/loop/loop.go b/loop/loop.go new file mode 100644 index 0000000..899f810 --- /dev/null +++ b/loop/loop.go @@ -0,0 +1,22 @@ +/* +Package loop shamelessly attempts to bring the better parts of Lisp loops to Go specifically in order to enable rapid, and clean applications development --- particularly when replacing shell scripts with Go. +*/ +package loop + +import "fmt" + +// Do executes the given function for each item in the slice. If any +// error is encountered processing stops and error returned. +func Do[T any](set []T, p func(i T) error) error { + for _, i := range set { + if err := p(i); err != nil { + return err + } + } + return nil +} + +// Println prints ever element of the set. +func Println[T any](set []T) { + Do(set, func(i T) error { fmt.Println(i); return nil }) +} diff --git a/loop/loop_test.go b/loop/loop_test.go new file mode 100644 index 0000000..849ec3e --- /dev/null +++ b/loop/loop_test.go @@ -0,0 +1,17 @@ +package loop_test + +import "github.com/rwxrob/bonzai/loop" + +func ExamplePrintln() { + set := []string{"doe", "ray", "mi"} + loop.Println(set) + bools := []bool{false, true, true} + loop.Println(bools) + // Output: + // doe + // ray + // mi + // false + // true + // true +} diff --git a/maps/maps.go b/maps/maps.go new file mode 100644 index 0000000..f21e1a3 --- /dev/null +++ b/maps/maps.go @@ -0,0 +1,28 @@ +package maps + +import ( + "path/filepath" + "sort" + + "github.com/rwxrob/bonzai/fn" +) + +// Prefix returns a new slice with prefix added to each string. +func Prefix(in []string, pre string) []string { + return fn.Map(in, func(i string) string { return pre + i }) +} + +// Keys returns the keys in lexicographically sorted order. +func Keys[T any](m map[string]T) []string { + keys := []string{} + for k, _ := range m { + keys = append(keys, k) + sort.Strings(keys) + } + return keys +} + +// CleanPaths runs filepath.Clean on each item in the slice and returns. +func CleanPaths(paths []string) []string { + return fn.Map(paths, func(i string) string { return filepath.Clean(i) }) +} diff --git a/maps/maps_test.go b/maps/maps_test.go new file mode 100644 index 0000000..38d81da --- /dev/null +++ b/maps/maps_test.go @@ -0,0 +1,39 @@ +package maps_test + +import ( + "fmt" + + "github.com/rwxrob/bonzai/maps" +) + +func ExampleKeys() { + m1 := map[string]int{"two": 2, "three": 3, "one": 1} + m2 := map[string]string{"two": "two", "three": "three", "one": "one"} + fmt.Println(maps.Keys(m1)) + fmt.Println(maps.Keys(m2)) + // Output: + // [one three two] + // [one three two] +} + +func ExamplePrefix() { + fmt.Println(maps.Prefix([]string{"foo", "bar"}, "my")) + // Output: + // [myfoo mybar] +} + +func ExampleCleanPaths() { + paths := []string{ + ``, + `.`, + `./`, + `./thing`, + `/sub/../../thing`, + } + filter.Println(filter.CleanPaths(paths)) + // Output: + // . + // . + // . + // /thing +} diff --git a/set/set.go b/set/set.go new file mode 100644 index 0000000..d404d57 --- /dev/null +++ b/set/set.go @@ -0,0 +1,24 @@ +package set + +type Text interface { + string | []byte +} + +// Minus performs a set "minus" operation by returning a new set with +// the elements of the second set removed from it. +func Minus[T Text, M Text](set []T, min []M) []T { + m := []T{} + for _, i := range set { + var seen bool + for _, n := range min { + if string(n) == string(i) { + seen = true + break + } + } + if !seen { + m = append(m, i) + } + } + return m +} diff --git a/set/set_test.go b/set/set_test.go new file mode 100644 index 0000000..08a7f54 --- /dev/null +++ b/set/set_test.go @@ -0,0 +1,16 @@ +package set_test + +import ( + "fmt" + + "github.com/rwxrob/bonzai/set" +) + +func ExampleMinus() { + s := []string{ + "one", "two", "three", "four", "five", "six", "seven", + } + fmt.Println(set.Minus(s, []string{"two", "four", "six"})) + // Output: + // [one three five seven] +} diff --git a/util/files.go b/util/files.go index 0a715da..df154a3 100644 --- a/util/files.go +++ b/util/files.go @@ -3,7 +3,7 @@ package util import ( "os" - "github.com/rwxrob/bonzai/filter" + "github.com/rwxrob/bonzai/filt" ) // Files returns a slice of strings matching the names of the files @@ -26,5 +26,5 @@ func Files(dir string) []string { } func FilesWith(dir, pre string) []string { - return filter.HasPrefix(Files(dir), pre) + return filt.HasPrefix(Files(dir), pre) }