bexp

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2024 License: Apache-2.0 Imports: 7 Imported by: 1

README

Bash Brace Expansion in Go

Go implementation of Brace Expansion mechanism to generate arbitrary strings.

Implementing: 3.5.1 Brace Expansion

PkgGoDev license Mentioned in Awesome Go

tests Go Report Card Coverage Status

Usage

go get github.com/happy-sdk/happy/pkg/strings/bexp

Get string slice

package main

import (
  "fmt"

  "github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
  var v []string
  v = bexp.Parse("file-{a,b,c}.jpg")
  fmt.Println(v)
  // [file-a.jpg file-b.jpg file-c.jpg]

  v = bexp.Parse("-v{,,}")
  fmt.Println(v)
  // [-v -v -v]

  v = bexp.Parse("file{0..2}.jpg")
  fmt.Println(v)
  // [file0.jpg file1.jpg file2.jpg]

  v = bexp.Parse("file{2..0}.jpg")
  fmt.Println(v)
  // [file2.jpg file1.jpg file0.jpg]

  v = bexp.Parse("file{0..4..2}.jpg")
  fmt.Println(v)
  // [file0.jpg file2.jpg file4.jpg]

  v = bexp.Parse("file-{a..e..2}.jpg")
  fmt.Println(v)
  // [file-a.jpg file-c.jpg file-e.jpg]

  v = bexp.Parse("file{00..10..5}.jpg")
  fmt.Println(v)
  // [file00.jpg file05.jpg file10.jpg]

  v = bexp.Parse("{{A..C},{a..c}}")
  fmt.Println(v)
  // [A B C a b c]

  v = bexp.Parse("ppp{,config,oe{,conf}}")
  fmt.Println(v)
  // [ppp pppconfig pppoe pppoeconf]

  v = bexp.Parse("data/{P1/{10..19},P2/{20..29},P3/{30..39}}")
  fmt.Println(v)
  // [data/P1/10 data/P1/11 data/P1/12 data/P1/13 data/P1/14 data/P1/15 data/P1/16 data/P1/17 data/P1/18 data/P1/19 data/P2/20 data/P2/21 data/P2/22 data/P2/23 data/P2/24 data/P2/25 data/P2/26 data/P2/27 data/P2/28 data/P2/29 data/P3/30 data/P3/31 data/P3/32 data/P3/33 data/P3/34 data/P3/35 data/P3/36 data/P3/37 data/P3/38 data/P3/39]
}

Generating directory tree

package main

import (
  "log"

  "github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
  const (
    rootdir = "/tmp/bexp"
    treeexp = rootdir + "/{dir1,dir2,dir3/{subdir1,subdir2}}"
  )
  if err := bexp.MkdirAll(treeexp, 0750); err != nil {
    log.Fatal(err)
  }

  // Will produce directory tree
  // /tmp/bexp
  // /tmp/bexp/dir1
  // /tmp/bexp/dir2
  // /tmp/bexp/dir3
  // /tmp/bexp/dir3/subdir1
  // /tmp/bexp/dir3/subdir2
}

Expand URLS

The example shows how to create Openstreetmap tiles
around the desired latitude and longitude coordinates.

package main

import (
  "fmt"
  "log"
  "math"

  "github.com/happy-sdk/happy/pkg/strings/bexp"
)

func getCenterTile(lat, long float64, zoom int) (z, x, y int) {
  n := math.Exp2(float64(zoom))
  x = int(math.Floor((long + 180.0) / 360.0 * n))
  if float64(x) >= n {
    x = int(n - 1)
  }
  y = int(math.Floor((1.0 - math.Log(
    math.Tan(lat*math.Pi/180.0)+
      1.0/math.Cos(lat*math.Pi/180.0))/
    math.Pi) / 2.0 * n))
  return x, y, zoom
}

func main() {
  x, y, z := getCenterTile(51.03, 13.78, 5)
  pattern := fmt.Sprintf(
    "https://tile.openstreetmap.org/%d/{%d..%d}/{%d..%d}.png",
    z, x-2, x+2, y-2, y+2,
  )
  tiles := bexp.Parse(pattern)
  fmt.Println("pattern: ", pattern)
  for _, tile := range tiles {
    fmt.Println(tile)
  }
  // Output:
  // pattern: https://tile.openstreetmap.org/5/{15..19}/{8..12}.png
  // https://tile.openstreetmap.org/5/15/8.png
  // https://tile.openstreetmap.org/5/15/9.png
  // https://tile.openstreetmap.org/5/15/10.png
  // https://tile.openstreetmap.org/5/15/11.png
  // https://tile.openstreetmap.org/5/15/12.png
  // https://tile.openstreetmap.org/5/16/8.png
  // https://tile.openstreetmap.org/5/16/9.png
  // https://tile.openstreetmap.org/5/16/10.png
  // https://tile.openstreetmap.org/5/16/11.png
  // https://tile.openstreetmap.org/5/16/12.png
  // https://tile.openstreetmap.org/5/17/8.png
  // https://tile.openstreetmap.org/5/17/9.png
  // https://tile.openstreetmap.org/5/17/10.png
  // https://tile.openstreetmap.org/5/17/11.png
  // https://tile.openstreetmap.org/5/17/12.png
  // https://tile.openstreetmap.org/5/18/8.png
  // https://tile.openstreetmap.org/5/18/9.png
  // https://tile.openstreetmap.org/5/18/10.png
  // https://tile.openstreetmap.org/5/18/11.png
  // https://tile.openstreetmap.org/5/18/12.png
  // https://tile.openstreetmap.org/5/19/8.png
  // https://tile.openstreetmap.org/5/19/9.png
  // https://tile.openstreetmap.org/5/19/10.png
  // https://tile.openstreetmap.org/5/19/11.png
  // https://tile.openstreetmap.org/5/19/12.png
}

Need error checking?

package main

import (
  "errors"
  "fmt"

  "github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
  empty, err := bexp.ParseValid("")
  fmt.Printf("%q - %t\n", empty[0], errors.Is(err, bexp.ErrEmptyResult))

  abc, err := bexp.ParseValid("abc")
  fmt.Printf("%q - %t\n", abc[0], errors.Is(err, bexp.ErrUnchangedBraceExpansion))

  // Output:
  // "" - true
  // "abc" - true
}

With os.Expand

os.Expand

package main

import (
  "fmt"
  "os"

  "github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
  const treeExp = "$MY_ROOT_DIR/dir{1..3}/{subdir1,subdir2}"
  mapper := func(varName string) string {
    switch varName {
    case "MY_ROOT_DIR":
      return "/my_root"
    }
    return ""
  }
  str := os.Expand(treeExp, mapper)
  fmt.Println("str := os.Expand(treeExp, mapper)")
  fmt.Println(str)

  fmt.Println("v := bexp.Parse(str)")
  v := bexp.Parse(str)
  for _, p := range v {
    fmt.Println(p)
  }

  // Output:
  // str := os.Expand(treeExp, mapper)
  // /my_root/dir{1..3}/{subdir1,subdir2}
  // v := bexp.Parse(str)
  // /my_root/dir1/subdir1
  // /my_root/dir1/subdir2
  // /my_root/dir2/subdir1
  // /my_root/dir2/subdir2
  // /my_root/dir3/subdir1
  // /my_root/dir3/subdir2
}

With os.ExpandEnv

os.ExpandEnv

package main

import (
  "fmt"
  "os"

  "github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
  const treeExp = "$MY_ROOT_DIR/dir{1..3}/{subdir1,subdir2}"
  os.Setenv("MY_ROOT_DIR", "/my_root")
  
  str := os.ExpandEnv(treeExp)
  fmt.Println("str := os.ExpandEnv(treeExp)")
  fmt.Println(str)
  
  fmt.Println("v := bexp.Parse(str)")
  v := bexp.Parse(str)
  for _, p := range v {
    fmt.Println(p)
  }
  
  // Output:
  // str := os.ExpandEnv(treeExp)
  // /my_root/dir{1..3}/{subdir1,subdir2}
  // v := bexp.Parse(str)
  // /my_root/dir1/subdir1
  // /my_root/dir1/subdir2
  // /my_root/dir2/subdir1
  // /my_root/dir2/subdir2
  // /my_root/dir3/subdir1
  // /my_root/dir3/subdir2
}

Inspired by and other similar libraries

following package were inspiration to create this package, The motivation of this package is to improve performance and reduce memory allocations compared to other solutions. Also to add some commonly used API's when working with brace expansion strings

Documentation

Overview

Package bexp implements Brace Expansion mechanism to generate arbitrary strings. Patterns to be brace expanded take the form of an optional preamble, followed by either a series of comma-separated strings or a sequence expression between a pair of braces, followed by an optional postscript. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.

Brace expansions may be nested. The results of each expanded string are not sorted; left to right order is preserved. For example,

Parse("a{d,c,b}e")
[]string{"ade", "ace", "abe"}

Any incorrectly formed brace expansion is left unchanged.

More info about Bash Brace Expansion can be found at https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyResult representing empty result by parser.
	ErrEmptyResult = errors.New("result is empty")
	// ErrUnchangedBraceExpansion for any incorrectly formed brace expansion
	// where input string is left unchanged.
	ErrUnchangedBraceExpansion = errors.New("brace expansion left unchanged")
)

Functions

func MkdirAll

func MkdirAll(expr string, perm os.FileMode) error

MkdirAll calls os.MkdirAll on each math from provided string to create a directory tree from brace expansion. Error can be ErrEmptyResult if parsing provided str results no paths or first error of os.MkdirAll.

Example
package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
	const (
		rootdir = "/tmp/bexp"
		treeExp = rootdir + "/{dir1,dir2,dir3/{subdir1,subdir2}}"
	)
	if err := bexp.MkdirAll(treeExp, 0750); err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(rootdir)

	if err := bexp.MkdirAll(rootdir+"/path/unmodified", 0750); err != nil {
		log.Println(err)
		return
	}

	err := filepath.Walk(rootdir,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			fmt.Println(path)
			return nil
		})
	if err != nil {
		log.Println(err)
		return
	}

}
Output:

/tmp/bexp
/tmp/bexp/dir1
/tmp/bexp/dir2
/tmp/bexp/dir3
/tmp/bexp/dir3/subdir1
/tmp/bexp/dir3/subdir2
/tmp/bexp/path
/tmp/bexp/path/unmodified

func Parse

func Parse(expr string) []string

Parse string expresion into BraceExpansion result.

Example
package main

import (
	"fmt"

	"github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
	var v []string

	v = bexp.Parse("/path/unmodified")
	fmt.Println(v)

	v = bexp.Parse("file-{a,b,c}.jpg")
	fmt.Println(v)

	v = bexp.Parse("-v{,,}")
	fmt.Println(v)

	v = bexp.Parse("file{0..2}.jpg")
	fmt.Println(v)

	v = bexp.Parse("file{2..0}.jpg")
	fmt.Println(v)

	v = bexp.Parse("file{0..4..2}.jpg")
	fmt.Println(v)

	v = bexp.Parse("file-{a..e..2}.jpg")
	fmt.Println(v)

	v = bexp.Parse("file{00..10..5}.jpg")
	fmt.Println(v)

	v = bexp.Parse("{{A..C},{a..c}}")
	fmt.Println(v)

	v = bexp.Parse("ppp{,config,oe{,conf}}")
	fmt.Println(v)

	v = bexp.Parse("data/{P1/{10..19},P2/{20..29},P3/{30..39}}")
	fmt.Println(v)

}
Output:

[/path/unmodified]
[file-a.jpg file-b.jpg file-c.jpg]
[-v -v -v]
[file0.jpg file1.jpg file2.jpg]
[file2.jpg file1.jpg file0.jpg]
[file0.jpg file2.jpg file4.jpg]
[file-a.jpg file-c.jpg file-e.jpg]
[file00.jpg file05.jpg file10.jpg]
[A B C a b c]
[ppp pppconfig pppoe pppoeconf]
[data/P1/10 data/P1/11 data/P1/12 data/P1/13 data/P1/14 data/P1/15 data/P1/16 data/P1/17 data/P1/18 data/P1/19 data/P2/20 data/P2/21 data/P2/22 data/P2/23 data/P2/24 data/P2/25 data/P2/26 data/P2/27 data/P2/28 data/P2/29 data/P3/30 data/P3/31 data/P3/32 data/P3/33 data/P3/34 data/P3/35 data/P3/36 data/P3/37 data/P3/38 data/P3/39]
Example (ExpandOsmTiles)

ExampleExpandOsmTiles the example shows how to create Openstreetmap tiles around the desired latitude and longitude coordinates.

https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png https://a.tile.openstreetmap.org/1/1/1.png

package main

import (
	"fmt"
	"math"

	"github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
	x, y, z := getCenterTile(51.03, 13.78, 5)
	pattern := fmt.Sprintf(
		"https://tile.openstreetmap.org/%d/{%d..%d}/{%d..%d}.png",
		z, x-2, x+2, y-2, y+2,
	)

	tiles := bexp.Parse(pattern)

	fmt.Println("pattern:", pattern)
	for _, tile := range tiles {
		fmt.Println(tile)
	}
}

func getCenterTile(lat, long float64, zoom int) (z, x, y int) {
	n := math.Exp2(float64(zoom))
	x = int(math.Floor((long + 180.0) / 360.0 * n))
	if float64(x) >= n {
		x = int(n - 1)
	}
	y = int(math.Floor((1.0 - math.Log(
		math.Tan(lat*math.Pi/180.0)+
			1.0/math.Cos(lat*math.Pi/180.0))/
		math.Pi) / 2.0 * n))
	return x, y, zoom
}
Output:

pattern: https://tile.openstreetmap.org/5/{15..19}/{8..12}.png
https://tile.openstreetmap.org/5/15/8.png
https://tile.openstreetmap.org/5/15/9.png
https://tile.openstreetmap.org/5/15/10.png
https://tile.openstreetmap.org/5/15/11.png
https://tile.openstreetmap.org/5/15/12.png
https://tile.openstreetmap.org/5/16/8.png
https://tile.openstreetmap.org/5/16/9.png
https://tile.openstreetmap.org/5/16/10.png
https://tile.openstreetmap.org/5/16/11.png
https://tile.openstreetmap.org/5/16/12.png
https://tile.openstreetmap.org/5/17/8.png
https://tile.openstreetmap.org/5/17/9.png
https://tile.openstreetmap.org/5/17/10.png
https://tile.openstreetmap.org/5/17/11.png
https://tile.openstreetmap.org/5/17/12.png
https://tile.openstreetmap.org/5/18/8.png
https://tile.openstreetmap.org/5/18/9.png
https://tile.openstreetmap.org/5/18/10.png
https://tile.openstreetmap.org/5/18/11.png
https://tile.openstreetmap.org/5/18/12.png
https://tile.openstreetmap.org/5/19/8.png
https://tile.openstreetmap.org/5/19/9.png
https://tile.openstreetmap.org/5/19/10.png
https://tile.openstreetmap.org/5/19/11.png
https://tile.openstreetmap.org/5/19/12.png
Example (OsExpand)
package main

import (
	"fmt"
	"os"

	"github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
	const treeExp = "$MY_ROOT_DIR/dir{1..3}/{subdir1,subdir2}"
	mapper := func(varName string) string {
		if varName == "MY_ROOT_DIR" {
			return "/my_root"
		}
		return ""
	}
	str := os.Expand(treeExp, mapper)
	fmt.Println("str := os.Expand(treeExp, mapper)")
	fmt.Println(str)

	fmt.Println("v := bexp.Parse(str)")
	v := bexp.Parse(str)
	for _, p := range v {
		fmt.Println(p)
	}

}
Output:

str := os.Expand(treeExp, mapper)
/my_root/dir{1..3}/{subdir1,subdir2}
v := bexp.Parse(str)
/my_root/dir1/subdir1
/my_root/dir1/subdir2
/my_root/dir2/subdir1
/my_root/dir2/subdir2
/my_root/dir3/subdir1
/my_root/dir3/subdir2
Example (OsExpandEnv)
package main

import (
	"fmt"
	"os"

	"github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
	const treeExp = "$MY_ROOT_DIR/dir{1..3}/{subdir1,subdir2}"
	os.Setenv("MY_ROOT_DIR", "/my_root")

	str := os.ExpandEnv(treeExp)
	fmt.Println("str := os.ExpandEnv(treeExp)")
	fmt.Println(str)

	fmt.Println("v := bexp.Parse(str)")
	v := bexp.Parse(str)
	for _, p := range v {
		fmt.Println(p)
	}

}
Output:

str := os.ExpandEnv(treeExp)
/my_root/dir{1..3}/{subdir1,subdir2}
v := bexp.Parse(str)
/my_root/dir1/subdir1
/my_root/dir1/subdir2
/my_root/dir2/subdir1
/my_root/dir2/subdir2
/my_root/dir3/subdir1
/my_root/dir3/subdir2

func ParseValid

func ParseValid(expr string) (res []string, err error)

ParseValid is for convienience to get errors on input: 1. ErrEmptyResult when provided string is empty When working with Brace Expansions this method is for convinience to handle only empty string as errors in your program. Note that even then it is actually not invalid. As Brace Expansion docs say: "Any incorrectly formed brace expansion is left unchanged.".

2. ErrUnchangedBraceExpansion when provided string was left unchanged Result will always be `BraceExpansion` with min len 1 to satisfy "Any incorrectly formed brace expansion is left unchanged.".

Example
package main

import (
	"errors"
	"fmt"

	"github.com/happy-sdk/happy/pkg/strings/bexp"
)

func main() {
	empty, err := bexp.ParseValid("")
	fmt.Printf("%q - %t\n", empty[0], errors.Is(err, bexp.ErrEmptyResult))

	abc, err := bexp.ParseValid("abc")
	fmt.Printf("%q - %t\n", abc[0], errors.Is(err, bexp.ErrUnchangedBraceExpansion))

}
Output:

"" - true
"abc" - true

Types

type BalancedResult

type BalancedResult struct {
	Valid bool   // is BalancedResult valid
	Start int    // the index of the first match of a
	End   int    // the index of the matching b
	Pre   string // the preamble, a and b not included
	Body  string // the match, a and b not included
	Post  string // the postscript, a and b not included
}

BalancedResult is returned for the first non-nested matching pair of a and b in str.

func Balanced

func Balanced(a, b, str string) BalancedResult

Balanced returns first non-nested matching pair of a and b in str.

func Range

func Range(a, b, str string) BalancedResult

Range retruns the first non-nested matching pair of a and b in str.

type BraceExpansion

type BraceExpansion []string

BraceExpansion represents bash style brace expansion.

func (BraceExpansion) Result

func (b BraceExpansion) Result() []string

Result is convience to get result as string slice.

func (BraceExpansion) String

func (b BraceExpansion) String() string

String calls strings.Join(b, " ") and returns resulting string.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL