Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add glob support #116

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 65 additions & 45 deletions cmd/byctl/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,10 @@ var lsCmd = &cli.Command{
return err
}

format := shortListFormat
if c.Bool("l") || c.String("format") == "long" {
format = longListFormat
}

isFirstSrc := true
for i := 0; i < c.Args().Len(); i++ {
conn, path, err := cfg.ParseProfileInput(c.Args().Get(i))
// parse args
var storePathMap = make(map[*operations.SingleOperator][]string)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map here looks strange. I'm not sure whether to expand all the globs first, or one by one (expand one and then execute List).

for _, arg := range c.Args().Slice() {
conn, path, err := cfg.ParseProfileInput(arg)
if err != nil {
logger.Error("parse profile input", zap.Error(err))
continue
Expand All @@ -68,52 +64,76 @@ var lsCmd = &cli.Command{

so := operations.NewSingleOperator(store)

ch, err := so.List(path)
if err != nil {
logger.Error("list",
zap.String("path", path),
zap.Error(err))
continue
}

// print src path if more than 1 arg
if c.Args().Len() > 1 {
if isFirstSrc {
isFirstSrc = false
} else {
fmt.Printf("\n")
var args []string
args = append(args, path)
if hasMeta(path) {
args, err = so.Glob(path)
if err != nil {
logger.Error("glob", zap.Error(err))
fmt.Printf("ls: cannot access '%s': No such file or directory\n", path)
continue
}
fmt.Printf("%s:\n", c.Args().Get(i))
}

isFirst := true
var totalNum int
var totalSize int64
storePathMap[so] = args
}

for v := range ch {
if v.Error != nil {
logger.Error("read next result", zap.Error(v.Error))
break
}
format := shortListFormat
if c.Bool("l") || c.String("format") == "long" {
format = longListFormat
}

oa := parseObject(v.Object)
fmt.Print(oa.Format(format, isFirst))
isFirstSrc := true
for so, paths := range storePathMap {
for _, path := range paths {
ch, err := so.List(path)
if err != nil {
logger.Error("list",
zap.String("path", path),
zap.Error(err))
continue
}

// Update isFirst
if isFirst {
isFirst = false
// print src path if more than 1 arg
if len(storePathMap) > 1 || len(paths) > 1 {
if isFirstSrc {
isFirstSrc = false
} else {
fmt.Printf("\n")
}
// so.StatStorager.Service + ":" + path
fmt.Printf("%s:\n", path)
}

totalNum += 1
totalSize += oa.size
}
// End of line
fmt.Print("\n")
isFirst := true
var totalNum int
var totalSize int64

// display summary information
if c.Bool(lsFlagSummarize) {
fmt.Printf("\n%14s %d\n", "Total Objects:", totalNum)
fmt.Printf("%14s %s\n", "Total Size:", units.BytesSize(float64(totalSize)))
for v := range ch {
if v.Error != nil {
logger.Error("read next result", zap.Error(v.Error))
break
}

oa := parseObject(v.Object)
fmt.Print(oa.Format(format, isFirst))

// Update isFirst
if isFirst {
isFirst = false
}

totalNum += 1
totalSize += oa.size
}
// End of line
fmt.Print("\n")

// display summary information
if c.Bool(lsFlagSummarize) {
fmt.Printf("\n%14s %d\n", "Total Objects:", totalNum)
fmt.Printf("%14s %s\n", "Total Size:", units.BytesSize(float64(totalSize)))
}
}
}
return
Expand Down
10 changes: 10 additions & 0 deletions cmd/byctl/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"fmt"
"runtime"
"strings"
"time"

"github.com/Xuanwo/go-bufferpool"
Expand Down Expand Up @@ -50,3 +52,11 @@ func parseLimit(text string) (types.Pair, error) {
}
}), nil
}

func hasMeta(path string) bool {
magicChars := `*?[{`
if runtime.GOOS != "windows" {
magicChars = `*?[{\`
}
return strings.ContainsAny(path, magicChars)
}
27 changes: 14 additions & 13 deletions docs/rfcs/96-add-glob-patterns-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

## Background

Globs, also known as glob patterns, are patterns that can expand a wildcard pattern into a list of path that match the given pattern.
### Glob patterns

In the command shell, a wildcard is a short textual pattern, that can match another character (or characters) in a file path. It’s kind of a shortcut that allows you to specify a whole set of related path names using a single, concise pattern.
Globs, also known as glob patterns, are patterns that can expand a wildcard pattern into a list of path that match the given pattern.

A string can be considered a wildcard pattern if it contains one of the following characters (unquoted and unescaped): `*`, `?`, `[` or `{`:

Expand All @@ -22,6 +22,15 @@ A string can be considered a wildcard pattern if it contains one of the followin
{ } - (curly brackets) matches on any of a series of sub-patterns you specify, e.g. {a,b,c} matches one a, one b and one c.
```

### Wildcards in cmd arguments

In the command shell, a wildcard is a short textual pattern, that can match another character (or characters) in a file path. It’s kind of a shortcut that allows you to specify a whole set of related path names using a single, concise pattern.

When the shell sees either of these characters unquoted and unescaped in a command line argument:

- It attempts to expand the argument by interpreting it as a path and matching the wildcard to all possible files in the path. The resulting set of file paths is then sent to the target command as a list of arguments.
- Brace expansion `{...}` works differently to normal wildcards, in that the shell expands the braces before even looking for files: it actually generates all the permutations of the pattern you specify and then performs wildcard expansion on the results.

## Proposal

I propose to add glob support by using UNIX style wildcards in the path arguments of the command.
Expand All @@ -36,18 +45,10 @@ Each wildcard will be evaluated against the source path. The following pattern s
- {...}: Brace expansion, terms are separated by commas (without spaces) and each term must be the name of something or a wildcard.
- \: Backslash, used as an "escape" character.

**Notice:**
Instead of expanding the braces before even looking for files, byctl attempts to determine whether the listed file name matches the file name pattern.

Glob patterns can be used in the following commands:
### Implementation

- cat
- cp
- ls
- mv
- rm
- stat
- sync
- byctl will expand the argument by matching the wildcard to all possible file paths or objects in service. The resulting set of file paths will then send to the target command as a list of arguments.
- Instead of expanding the braces `{...}` before even looking for files, byctl attempts to determine whether the listed file name matches the file name pattern.

### Examples

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.15
require (
github.com/BurntSushi/toml v0.4.1
github.com/Xuanwo/go-bufferpool v0.2.0
github.com/bmatcuk/doublestar/v4 v4.0.2
github.com/docker/go-units v0.4.0
github.com/google/uuid v1.3.0
github.com/panjf2000/ants/v2 v2.4.6
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA=
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
Expand Down
34 changes: 34 additions & 0 deletions operations/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package operations

import (
"path/filepath"

"github.com/bmatcuk/doublestar/v4"
)

// Glob returns the names of all files matching pattern or nil
// if there is no matching file.
func (so *SingleOperator) Glob(path string) (matches []string, err error) {
unixPath := filepath.ToSlash(path)
base, _ := doublestar.SplitPattern(unixPath)
if base == "." {
base = ""
}

ch, err := so.ListRecursively(base)
if err != nil {
return
}

for v := range ch {
if v.Error != nil {
return nil, v.Error
}

if ok, _ := doublestar.Match(unixPath, v.Object.Path); ok {
matches = append(matches, v.Object.Path)
}
}

return matches, nil
}