1
0
mirror of https://github.com/minio/mc.git synced 2025-11-25 08:23:07 +03:00
Files
mc/cmd/tree-main.go
Anis Elleuch 712ff33319 cli: Load global flags in initBeforeRunningCmd (#3283)
app.Before receives a cli.Context but without flags parsed. There is no
point calling ctx.Bool() or ctx.IsSet() at that stage.

However, flags are parsed in commands, so minio initialization and
setting global variables (globalJSON, globalQuiet, etc..) can be moved
to the Before function of all commands.

Avoid setting command.Before for non leaf commands, otherwise Before
function will be called multiples times until it reaches the leaf
command.
2020-07-01 17:28:43 -07:00

288 lines
7.3 KiB
Go

/*
* MinIO Client (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/fatih/color"
"github.com/minio/cli"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio/pkg/console"
)
const (
treeEntry = "├─ "
treeLastEntry = "└─ "
treeNext = "│"
treeLevel = " "
)
// Structured message depending on the type of console.
type treeMessage struct {
Entry string
IsDir bool
BranchString string
}
// Colorized message for console printing.
func (t treeMessage) String() string {
entryType := "File"
if t.IsDir {
entryType = "Dir"
}
return fmt.Sprintf("%s%s", t.BranchString, console.Colorize(entryType, t.Entry))
}
// JSON'ified message for scripting.
// Does No-op. JSON requests are redirected to `ls -r --json`
func (t treeMessage) JSON() string {
fatalIf(probe.NewError(errors.New("JSON() should never be called here")), "Unable to list in tree format. Please report this issue at https://github.com/minio/mc/issues")
return ""
}
var treeFlags = []cli.Flag{
cli.BoolFlag{
Name: "files, f",
Usage: "includes files in tree",
},
cli.IntFlag{
Name: "depth, d",
Usage: "sets the depth threshold",
Value: -1,
},
}
// trees files and folders.
var treeCmd = cli.Command{
Name: "tree",
Usage: "list buckets and objects in a tree format",
Action: mainTree,
Before: initBeforeRunningCmd,
Flags: append(treeFlags, globalFlags...),
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} [FLAGS] TARGET [TARGET ...]
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES:
1. List all buckets and directories on MinIO object storage server in tree format.
{{.Prompt}} {{.HelpName}} myminio
2. List all directories in "mybucket" on MinIO object storage server in tree format.
{{.Prompt}} {{.HelpName}} myminio/mybucket/
3. List all directories in "mybucket" on MinIO object storage server hosted on Microsoft Windows in tree format.
{{.Prompt}} {{.HelpName}} myminio\mybucket\
4. List all directories and objects in "mybucket" on MinIO object storage server in tree format.
{{.Prompt}} {{.HelpName}} --files myminio/mybucket/
5. List all directories upto depth level '2' in tree format.
{{.Prompt}} {{.HelpName}} --depth 2 myminio/mybucket/
`,
}
// checkTreeSyntax - validate all the passed arguments
func checkTreeSyntax(ctx context.Context, cliCtx *cli.Context) {
args := cliCtx.Args()
if cliCtx.IsSet("depth") {
if cliCtx.Int("depth") < -1 || cliCtx.Int("depth") == 0 {
fatalIf(errInvalidArgument().Trace(args...), "please set a proper depth, for example: '--depth 1' to limit the tree output, default (-1) output displays everything")
}
}
if (args.Present()) && len(args) == 0 {
return
}
for _, url := range args {
if _, _, err := url2Stat(ctx, url, false, nil); err != nil && !isURLPrefixExists(url, false) {
fatalIf(err.Trace(url), "Unable to tree `"+url+"`.")
}
}
}
// doTree - list all entities inside a folder in a tree format.
func doTree(ctx context.Context, url string, level int, leaf bool, branchString string, depth int, includeFiles bool) error {
targetAlias, targetURL, _ := mustExpandAlias(url)
if !strings.HasSuffix(targetURL, "/") {
targetURL += "/"
}
clnt, err := newClientFromAlias(targetAlias, targetURL)
fatalIf(err.Trace(targetURL), "Unable to initialize target `"+targetURL+"`.")
prefixPath := clnt.GetURL().Path
separator := string(clnt.GetURL().Separator)
if !strings.HasSuffix(prefixPath, separator) {
prefixPath = filepath.Dir(prefixPath) + "/"
}
bucketNameShowed := false
var prev *ClientContent
show := func(end bool) error {
currbranchString := branchString
if level == 1 && !bucketNameShowed {
bucketNameShowed = true
printMsg(treeMessage{
Entry: url,
IsDir: true,
BranchString: branchString,
})
}
isLevelClosed := strings.HasSuffix(currbranchString, treeLastEntry)
if isLevelClosed {
currbranchString = strings.TrimSuffix(currbranchString, treeLastEntry)
} else {
currbranchString = strings.TrimSuffix(currbranchString, treeEntry)
}
if level != 1 {
if isLevelClosed {
currbranchString += " " + treeLevel
} else {
currbranchString += treeNext + treeLevel
}
}
if end {
currbranchString += treeLastEntry
} else {
currbranchString += treeEntry
}
// Convert any os specific delimiters to "/".
contentURL := filepath.ToSlash(prev.URL.Path)
prefixPath = filepath.ToSlash(prefixPath)
// Trim prefix of current working dir
prefixPath = strings.TrimPrefix(prefixPath, "."+separator)
if prev.Type.IsDir() {
printMsg(treeMessage{
Entry: strings.TrimSuffix(strings.TrimPrefix(contentURL, prefixPath), "/"),
IsDir: true,
BranchString: currbranchString,
})
} else {
printMsg(treeMessage{
Entry: strings.TrimPrefix(contentURL, prefixPath),
IsDir: false,
BranchString: currbranchString,
})
}
if prev.Type.IsDir() {
url := ""
if targetAlias != "" {
url = targetAlias + "/" + contentURL
} else {
url = contentURL
}
if depth == -1 || level <= depth {
if err := doTree(ctx, url, level+1, end, currbranchString, depth, includeFiles); err != nil {
return err
}
}
}
return nil
}
for content := range clnt.List(ctx, false, false, false, DirNone) {
if !includeFiles && !content.Type.IsDir() {
continue
}
if content.Err != nil {
errorIf(content.Err.Trace(clnt.GetURL().String()), "Unable to tree.")
continue
}
if prev != nil {
if err := show(false); err != nil {
return err
}
}
prev = content
}
if prev != nil {
if err := show(true); err != nil {
return err
}
}
return nil
}
// mainTree - is a handler for mc tree command
func mainTree(cliCtx *cli.Context) error {
ctx, cancelList := context.WithCancel(globalContext)
defer cancelList()
// check 'tree' cliCtx arguments.
checkTreeSyntax(ctx, cliCtx)
console.SetColor("File", color.New(color.Bold))
console.SetColor("Dir", color.New(color.FgCyan, color.Bold))
args := cliCtx.Args()
// mimic operating system tool behavior.
if !cliCtx.Args().Present() {
args = []string{"."}
}
includeFiles := cliCtx.Bool("files")
depth := cliCtx.Int("depth")
var cErr error
for _, targetURL := range args {
if !globalJSON {
if e := doTree(ctx, targetURL, 1, false, "", depth, includeFiles); e != nil {
cErr = e
}
} else {
targetAlias, targetURL, _ := mustExpandAlias(targetURL)
if !strings.HasSuffix(targetURL, "/") {
targetURL += "/"
}
clnt, err := newClientFromAlias(targetAlias, targetURL)
fatalIf(err.Trace(targetURL), "Unable to initialize target `"+targetURL+"`.")
if e := doList(ctx, clnt, true, false); e != nil {
cErr = e
}
}
}
return cErr
}