Improve organization of functional packages

This commit is contained in:
rwxrob 2022-02-27 01:17:49 -05:00
parent b5ac9f2ca6
commit 8e2e560e80
No known key found for this signature in database
GPG Key ID: 2B9111F33082AE77
12 changed files with 187 additions and 104 deletions

View File

@ -2,11 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
/*
Package fn implements the traditional map/filter/reduce/each functions
and an array type (A) for those who prefer a more object-oriented
approach. Unlike other implementations, the array (slice) is always
first preventing the first-class in-line anonymous function from
obfuscating the parameter list of the functional function.
"Why have a functional package in a commander?"
These functions are included because commander frameworks are about
creating less friction during command applications development. Since
many such commands are creating from ported shell scripts where the UNIX
philosophy and filters reign supreme in makes sense to enable similar
functional approaches (filters, pipelines) to approximate the simplicity
of and speed shell scripting development (at the very slight cost of
runtime performance). In short, this package speeds and simplifies
command application development by making it more compatible with shell
scripting in general.
*/
package fn

View File

@ -3,8 +3,27 @@ Package mapf contains nothing but map functions suitable for use with
the fn.Map generic function or the equivalent fn.A method. See the maps
package for functions that accept entire generic slices to be
transformed with mapf (or other) functions.
Note that any of the functions in this package can easily be added to a template.FuncMap for use in custom text|html/templates.
*/
package mapf
import (
"io/fs"
"strings"
)
// MarkDirs will add a slash (/) to the end of the name if the
// fs.DirEntry is a directory and return it as a string.
func MarkDirs(f fs.DirEntry) string {
if f.IsDir() {
return f.Name() + "/"
}
return f.Name()
}
// HashComment adds a "# " prefix.
func HashComment(i string) string { return "# " + i }
func HashComment(line string) string { return "# " + line }
// EscSpace puts backslash in front of any space.
func EscSpace(s string) string { return strings.ReplaceAll(s, ` `, `\ `) }

View File

@ -1,13 +1,32 @@
package mapf_test
import (
"os"
"github.com/rwxrob/bonzai/each"
"github.com/rwxrob/bonzai/fn"
"github.com/rwxrob/bonzai/mapf"
)
func ExampleMarkDirs() {
entries, _ := os.ReadDir("testdata/markdirs")
each.Println(fn.Map(entries, mapf.MarkDirs))
//Output:
// dir1/
// file1
}
func ExampleHashComment() {
fn.A[string]{"foo", "bar"}.M(mapf.HashComment).P()
each.Println(fn.Map([]string{"foo", "bar"}, mapf.HashComment))
// Output:
// # foo
// # bar
}
func ExampleEscSpace() {
s := []string{"one here", "and another one"}
each.Println(fn.Map(s, mapf.EscSpace))
// Output:
// one\ here
// and\ another\ \ \ \ one
}

0
mapf/testdata/markdirs/file1 vendored Normal file
View File

27
maps/complex.go Normal file
View File

@ -0,0 +1,27 @@
package maps
import (
"sort"
"github.com/rwxrob/bonzai/fn"
)
// Note to maintainers: This file contains maps that require additional
// arguments and are therefore not able to call simple map functions
// from the mapf package. Please keep simple mapf-able maps in
// simple.go instead.
// Prefix adds a prefix to the 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
}

View File

@ -2,12 +2,16 @@ package maps_test
import (
"fmt"
"os"
"github.com/rwxrob/bonzai/loop"
"github.com/rwxrob/bonzai/maps"
)
func ExamplePrefix() {
fmt.Println(maps.Prefix([]string{"foo", "bar"}, "my"))
// Output:
// [myfoo mybar]
}
func ExampleKeys() {
m1 := map[string]int{"two": 2, "three": 3, "one": 1}
m2 := map[string]string{"two": "two", "three": "three", "one": "one"}
@ -17,36 +21,3 @@ func ExampleKeys() {
// [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`,
}
loop.Println(maps.CleanPaths(paths))
// Output:
// .
// .
// .
// thing
// /thing
}
func ExampleMarkDirs() {
entries, _ := os.ReadDir("testdata/markdirs")
loop.Println(maps.MarkDirs(entries))
//Output:
// dir1/
// file1
}

View File

@ -1,41 +0,0 @@
package maps
import (
"io/fs"
"os"
"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) })
}
// MarkDirs will add an os.PathSeparator to the end of the name if the
// fs.DirEntry is a directory.
func MarkDirs(entries []fs.DirEntry) []string {
return fn.Map(entries, func(f fs.DirEntry) string {
if f.IsDir() {
return f.Name() + string(os.PathSeparator)
}
return f.Name()
})
}

26
maps/simple.go Normal file
View File

@ -0,0 +1,26 @@
package maps
import (
"io/fs"
"path/filepath"
"github.com/rwxrob/bonzai/fn"
"github.com/rwxrob/bonzai/mapf"
)
// Note to maintainers: This file contains simple maps that are
// implemented in the mapf package. Please keep complex maps in
// complex.go instead.
// MarkDirs will add an os.PathSeparator to the end of the name if the
// fs.DirEntry is a directory.
func MarkDirs(s []fs.DirEntry) []string { return fn.Map(s, mapf.MarkDirs) }
// Base extracts the filepath.Base of each path.
func Base(s []string) []string { return fn.Map(s, filepath.Base) }
// HashComment add the "# " prefix to each.
func HashComment(s []string) []string { return fn.Map(s, mapf.HashComment) }
// EscSpace replaces all spaces with backslashed spaces.
func EscSpace(s []string) []string { return fn.Map(s, mapf.EscSpace) }

43
maps/simple_test.go Normal file
View File

@ -0,0 +1,43 @@
package maps_test
import (
"os"
"github.com/rwxrob/bonzai/each"
"github.com/rwxrob/bonzai/maps"
)
func ExampleMarkDirs() {
entries, _ := os.ReadDir("testdata/markdirs")
each.Println(maps.MarkDirs(entries))
//Output:
// dir1/
// file1
}
func ExampleBase() {
paths := []string{
`some/thing /here`,
`other/thing`,
`foo`,
}
each.Println(maps.Base(paths))
//Output:
// here
// thing
// foo
}
func ExampleHashComment() {
each.Println(maps.HashComment([]string{"foo", "bar"}))
// Output:
// # foo
// # bar
}
func ExampleEscSpace() {
each.Println(maps.EscSpace([]string{"some thing", "one other thing"}))
// Output:
// some\ thing
// one\ other\ \ \ thing
}

View File

@ -2,27 +2,33 @@ package util
import (
"os"
"path/filepath"
"github.com/rwxrob/bonzai/filt"
"github.com/rwxrob/bonzai/maps"
)
// Files returns a slice of strings matching the names of the files
// within the given directory adding a slash to the end of any
// directories.
// directories and escaping any spaces by adding backslash. Note that
// this (and all functions of the bonzai package) assume forward slash
// path separators because no path argument should ever be passed to any
// bonzai command or high-level library that does not use forward slash
// paths. Commands should always use the comp.Files completer instead of
// host shell completion.
func Files(dir string) []string {
dir = filepath.Clean(dir)
if dir == "" {
dir = "."
}
files := []string{}
entries, err := os.ReadDir(dir)
if err != nil {
return files
}
return maps.Prefix(maps.MarkDirs(entries), dir+string(os.PathSeparator))
}
//FilesWith takes the path of a directory and returns the name of the
//files with the matching prefix.
func FilesWith(dir, pre string) []string {
return filt.HasPrefix(Files(dir), filepath.Join(dir, pre))
names := maps.MarkDirs(entries)
if dir == "." {
return names
}
if dir[len(dir)-1] != '/' {
dir += "/"
}
return maps.EscSpace(maps.Prefix(names, dir))
}

View File

@ -21,26 +21,25 @@ func ExampleFiles() {
// testdata/files/some
}
func ExampleFiles_spaces() {
loop.Println(util.Files("testdata/files/dir1"))
// Output:
// testdata/files/dir1/some\ thing
}
func ExampleFiles_empty() {
os.Chdir("testdata/files")
defer os.Chdir("../..")
loop.Println(util.Files(""))
// Output:
// ./bar
// ./blah
// ./dir1/
// ./dir2/
// ./dir3/
// ./foo
// ./other
// ./some
}
func ExampleFilesWith() {
loop.Println(util.FilesWith("testdata/files", "b"))
// Output:
// testdata/files/bar
// testdata/files/blah
// bar
// blah
// dir1/
// dir2/
// dir3/
// foo
// other
// some
}
func ExampleFiles_not_Directory() {

0
util/testdata/files/dir1/some thing vendored Normal file
View File