// mainList - is a handler for mc ls command func mainList(ctx *cli.Context) { checkListSyntax(ctx) args := ctx.Args() // Operating system tool behavior if globalMimicFlag && !ctx.Args().Present() { args = []string{"."} } console.SetCustomTheme(map[string]*color.Color{ "File": color.New(color.FgWhite), "Dir": color.New(color.FgCyan, color.Bold), "Size": color.New(color.FgYellow), "Time": color.New(color.FgGreen), }) config := mustGetMcConfig() for _, arg := range args { targetURL, err := getCanonicalizedURL(arg, config.Aliases) fatalIf(err.Trace(arg), "Unable to parse argument ‘"+arg+"’.") // if recursive strip off the "..." err = doListCmd(stripRecursiveURL(targetURL), isURLRecursive(targetURL)) fatalIf(err.Trace(targetURL), "Unable to list target ‘"+targetURL+"’.") } }
func registerBefore(ctx *cli.Context) error { setMcConfigDir(ctx.GlobalString("config")) globalQuietFlag = ctx.GlobalBool("quiet") globalForceFlag = ctx.GlobalBool("force") globalAliasFlag = ctx.GlobalBool("alias") globalDebugFlag = ctx.GlobalBool("debug") globalJSONFlag = ctx.GlobalBool("json") themeName := ctx.GlobalString("theme") if globalDebugFlag { console.NoDebugPrint = false } switch { case console.IsValidTheme(themeName) != true: console.Errorf("Invalid theme, please choose from the following list: %s.\n", console.GetThemeNames()) return errInvalidTheme{Theme: themeName} default: err := console.SetTheme(themeName) if err != nil { console.Errorf("Failed to set theme ‘%s’.", themeName) return err } } // Migrate any old version of config / state files to newer format. migrate() checkConfig() return nil }
// mainCopy is bound to sub-command func mainCopy(ctx *cli.Context) { checkCopySyntax(ctx) setCopyPalette(ctx.GlobalString("colors")) session := newSessionV2() var e error session.Header.CommandType = "cp" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(), "One or more unknown URL types passed.") } doCopySession(session) session.Delete() }
// main for share download. func mainShareDownload(ctx *cli.Context) { // setup share data folder and file. shareDataSetup() // Additional command speific theme customization. shareSetColor() // check input arguments. checkShareDownloadSyntax(ctx) args := ctx.Args() config := mustGetMcConfig() url := args.Get(0) // default expiration is 7days expires := time.Duration(604800) * time.Second if len(args) == 2 { var err error expires, err = time.ParseDuration(args.Get(1)) fatalIf(probe.NewError(err), "Unable to parse time argument.") } targetURL := getAliasURL(url, config.Aliases) // if recursive strip off the "..." err := doShareDownloadURL(stripRecursiveURL(targetURL), isURLRecursive(targetURL), expires) fatalIf(err.Trace(targetURL), "Unable to generate URL for download.") return }
// main for share download. func mainShareDownload(ctx *cli.Context) error { // Set global flags from context. setGlobalsFromContext(ctx) // check input arguments. checkShareDownloadSyntax(ctx) // Initialize share config folder. initShareConfig() // Additional command speific theme customization. shareSetColor() // Set command flags from context. isRecursive := ctx.Bool("recursive") expiry := shareDefaultExpiry if ctx.String("expire") != "" { var e error expiry, e = time.ParseDuration(ctx.String("expire")) fatalIf(probe.NewError(e), "Unable to parse expire=‘"+ctx.String("expire")+"’.") } for _, targetURL := range ctx.Args() { err := doShareDownloadURL(targetURL, isRecursive, expiry) if err != nil { switch err.ToGoError().(type) { case APINotImplemented: fatalIf(err.Trace(), "Unable to share a non S3 url ‘"+targetURL+"’.") default: fatalIf(err.Trace(targetURL), "Unable to share target ‘"+targetURL+"’.") } } } return nil }
// main for share upload command. func mainShareUpload(ctx *cli.Context) { // setup share data folder and file. shareDataSetup() // Additional command speific theme customization. shareSetColor() // check input arguments. checkShareUploadSyntax(ctx) var expires time.Duration var err error args := ctx.Args() config := mustGetMcConfig() if strings.TrimSpace(args.Get(1)) == "" { expires = time.Duration(604800) * time.Second } else { expires, err = time.ParseDuration(strings.TrimSpace(args.Get(1))) if err != nil { fatalIf(probe.NewError(err), "Unable to parse time argument.") } } contentType := strings.TrimSpace(args.Get(2)) targetURL := getAliasURL(strings.TrimSpace(args.Get(0)), config.Aliases) e := doShareUploadURL(stripRecursiveURL(targetURL), isURLRecursive(targetURL), expires, contentType) fatalIf(e.Trace(targetURL), "Unable to generate URL for upload.") }
// mainCopy is bound to sub-command func mainCopy(ctx *cli.Context) { checkCopySyntax(ctx) console.SetCustomTheme(map[string]*color.Color{ "Copy": color.New(color.FgGreen, color.Bold), }) session := newSessionV2() var e error session.Header.CommandType = "cp" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(), "One or more unknown URL types passed.") } doCopyCmdSession(session) session.Delete() }
func mainConfigVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } config, err := loadConfigV2() fatalIf(err.Trace(), "Unable to load config", nil) // convert interface{} back to its original struct newConf := config type Version struct { Value string `json:"value"` } if globalJSONFlag { tB, e := json.Marshal( struct { Version Version `json:"version"` }{Version: Version{newConf.Version}}, ) fatalIf(probe.NewError(e), "Unable to construct version string.", nil) Println(string(tB)) return } Println(newConf.Version) }
// mainCopy is bound to sub-command func mainCopy(ctx *cli.Context) { checkCopySyntax(ctx) // Additional command speific theme customization. console.SetColor("Copy", color.New(color.FgGreen, color.Bold)) var e error session := newSessionV3() session.Header.CommandType = "cp" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // If force flag is set save it with in session session.Header.CommandBoolFlag.Key = "force" session.Header.CommandBoolFlag.Value = ctx.Bool("force") // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(), "One or more unknown URL types passed.") } doCopySession(session) session.Delete() }
func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *cli.Context a := cli.NewApp() a.Commands = []cli.Command{ { Name: "foo", Action: func(c *cli.Context) { context = c }, Flags: []cli.Flag{ cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", }, }, Before: func(_ *cli.Context) error { return nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) expect(t, context.Args().Get(0), "abcd") expect(t, context.String("lang"), "spanish") }
func runAnalyticsCmd(c *cli.Context) { conf, err := loadConfigV1() if err != nil { log.Fatal(err.Trace()) } s := connectToMongo(c) defer s.Close() var result LogMessage iter := db.Find(bson.M{"http.request.method": "GET"}).Iter() for iter.Next(&result) { if time.Since(result.StartTime) < time.Duration(24*time.Hour) { filters := strings.Split(c.GlobalString("filter"), ",") var skip bool for _, filter := range filters { if strings.Contains(result.HTTP.Request.RemoteAddr, filter) { skip = true break } } if skip { continue } if result.StatusMessage == "" || result.StatusMessage == "OK" { if strings.HasSuffix(result.HTTP.Request.RequestURI, "minio") || strings.HasSuffix(result.HTTP.Request.RequestURI, "minio.exe") || strings.HasSuffix(result.HTTP.Request.RequestURI, "mc") || strings.HasSuffix(result.HTTP.Request.RequestURI, "mc.exe") { if err := updateGoogleAnalytics(conf, result); err != nil { log.Fatal(err.Trace()) } } } } } }
// Main entry point for mirror command. func mainMirror(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'mirror' cli arguments. checkMirrorSyntax(ctx) // Additional command speific theme customization. console.SetColor("Mirror", color.New(color.FgGreen, color.Bold)) var e error session := newSessionV6() session.Header.CommandType = "mirror" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // Set command flags from context. isForce := ctx.Bool("force") session.Header.CommandBoolFlags["force"] = isForce // extract URLs. session.Header.CommandArgs = ctx.Args() doMirrorSession(session) session.Delete() }
// mainList - is a handler for mc ls command func mainList(ctx *cli.Context) { checkListSyntax(ctx) args := ctx.Args() // Operating system tool behavior if globalMimicFlag && !ctx.Args().Present() { args = []string{"."} } console.SetCustomTheme(map[string]*color.Color{ "File": color.New(color.FgWhite), "Dir": color.New(color.FgCyan, color.Bold), "Size": color.New(color.FgYellow), "Time": color.New(color.FgGreen), }) targetURLs, err := args2URLs(args) fatalIf(err.Trace(args...), "One or more unknown URL types passed.") for _, targetURL := range targetURLs { // if recursive strip off the "..." var clnt client.Client clnt, err = target2Client(stripRecursiveURL(targetURL)) fatalIf(err.Trace(targetURL), "Unable to initialize target ‘"+targetURL+"’.") err = doList(clnt, isURLRecursive(targetURL), len(targetURLs) > 1) fatalIf(err.Trace(clnt.URL().String()), "Unable to list target ‘"+clnt.URL().String()+"’.") } }
// main for rm command. func mainRm(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'rm' cli arguments. checkRmSyntax(ctx) // rm specific flags. isForce := ctx.Bool("force") isIncomplete := ctx.Bool("incomplete") isRecursive := ctx.Bool("recursive") isFake := ctx.Bool("fake") // Set color. console.SetColor("Remove", color.New(color.FgGreen, color.Bold)) // Support multiple targets. for _, url := range ctx.Args() { targetAlias, targetURL, _ := mustExpandAlias(url) if isRecursive && isForce { rmAll(targetAlias, targetURL, isRecursive, isIncomplete, isFake) } else { if err := rm(targetAlias, targetURL, isIncomplete, isFake); err != nil { errorIf(err.Trace(url), "Unable to remove ‘"+url+"’.") continue } printMsg(rmMessage{Status: "success", URL: url}) } } }
// mainMakeBucket is entry point for mb command. func mainMakeBucket(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'mb' cli arguments. checkMakeBucketSyntax(ctx) // Additional command speific theme customization. console.SetColor("MakeBucket", color.New(color.FgGreen, color.Bold)) for _, targetURL := range ctx.Args() { // Instantiate client for URL. clnt, err := newClient(targetURL) fatalIf(err.Trace(targetURL), "Invalid target ‘"+targetURL+"’.") // Make bucket. err = clnt.MakeBucket() // Upon error print error and continue. if err != nil { errorIf(err.Trace(targetURL), "Unable to make bucket ‘"+targetURL+"’.") continue } // Successfully created a bucket. printMsg(makeBucketMessage{Status: "success", Bucket: targetURL}) } }
// mainShare - main handler for mc share command func mainShare(ctx *cli.Context) { if !ctx.Args().Present() || ctx.Args().First() == "help" { cli.ShowAppHelp(ctx) } // sub-commands like "upload" and "download" have their own main. }
// checkPolicySyntax check for incoming syntax. func checkPolicySyntax(ctx *cli.Context) { argsLength := len(ctx.Args()) // Always print a help message when we have extra arguments if argsLength > 2 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) // last argument is exit code. } // Always print a help message when no arguments specified if argsLength < 1 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) } firstArg := ctx.Args().Get(0) // More syntax checking switch accessPerms(firstArg) { case accessNone, accessDownload, accessUpload, accessPublic: // Always expect two arguments when a policy permission is provided if argsLength != 2 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) } case "list": // Always expect an argument after list cmd if argsLength != 2 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) } default: if argsLength == 2 { fatalIf(errDummy().Trace(), "Unrecognized permission ‘"+string(firstArg)+"’. Allowed values are [none, download, upload, public].") } } }
func runCastCmd(ctx *cli.Context) { checkCastSyntax(ctx) session := newSessionV2() var err error session.Header.CommandType = "cast" session.Header.RootPath, err = os.Getwd() if err != nil { session.Close() session.Delete() console.Fatalf("Unable to get current working folder. %s\n", err) } // extract URLs. session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Close() session.Delete() console.Fatalf("One or more unknown URL types found in %s. %s\n", ctx.Args(), err) } doCastCmdSession(session) session.Close() session.Delete() }
func mainVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } setVersionPalette(ctx.GlobalString("colors")) if globalJSONFlag { tB, e := json.Marshal( struct { Version struct { Value string `json:"value"` Format string `json:"format"` } `json:"version"` }{ Version: struct { Value string `json:"value"` Format string `json:"format"` }{ Value: mcVersion, Format: "RFC2616", }, }, ) fatalIf(probe.NewError(e), "Unable to construct version string.") console.Println(string(tB)) return } msg := console.Colorize("Version", fmt.Sprintf("Version: %s\n", mcVersion)) msg += console.Colorize("Version", fmt.Sprintf("Release-Tag: %s", mcReleaseTag)) console.Println(msg) }
// mainConfig is the handle for "mc config" sub-command. writes configuration data in json format to config file. func mainConfig(ctx *cli.Context) { checkConfigSyntax(ctx) // set new custom coloring console.SetCustomTheme(map[string]*color.Color{ "Alias": color.New(color.FgCyan, color.Bold), "AliasMessage": color.New(color.FgGreen, color.Bold), "URL": color.New(color.FgWhite), }) arg := ctx.Args().First() tailArgs := ctx.Args().Tail() switch strings.TrimSpace(arg) { case "add": if strings.TrimSpace(tailArgs.First()) == "alias" { addAlias(tailArgs.Get(1), tailArgs.Get(2)) } case "remove": if strings.TrimSpace(tailArgs.First()) == "alias" { removeAlias(tailArgs.Get(1)) } case "list": if strings.TrimSpace(tailArgs.First()) == "alias" { listAliases() } } }
func mainEventsRemove(ctx *cli.Context) error { console.SetColor("Events", color.New(color.FgGreen, color.Bold)) setGlobalsFromContext(ctx) checkEventsRemoveSyntax(ctx) args := ctx.Args() path := args.Get(0) arn := "" if len(args) == 2 { arn = args.Get(1) } client, err := newClient(path) if err != nil { fatalIf(err.Trace(), "Cannot parse the provided url.") } s3Client, ok := client.(*s3Client) if !ok { fatalIf(errDummy().Trace(), "The provided url doesn't point to a S3 server.") } err = s3Client.RemoveNotificationConfig(arn) fatalIf(err, "Cannot enable notification on the specified bucket.") printMsg(eventsRemoveMessage{ARN: arn}) return nil }
func mainVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } t, _ := time.Parse(time.RFC3339Nano, Version) if t.IsZero() { console.Println("") return } type Version struct { Value time.Time `json:"value"` Format string `json:"format"` } if globalJSONFlag { tB, e := json.Marshal( struct { Version Version `json:"version"` }{Version: Version{t, "RFC3339Nano"}}, ) fatalIf(probe.NewError(e), "Unable to construct version string.") console.Println(string(tB)) return } console.Println(t.Format(http.TimeFormat)) }
// mainDiff main for 'diff'. func mainDiff(ctx *cli.Context) { checkDiffSyntax(ctx) // Additional command speific theme customization. console.SetColor("DiffMessage", color.New(color.FgGreen, color.Bold)) console.SetColor("DiffOnlyInFirst", color.New(color.FgRed, color.Bold)) console.SetColor("DiffType", color.New(color.FgYellow, color.Bold)) console.SetColor("DiffSize", color.New(color.FgMagenta, color.Bold)) config := mustGetMcConfig() firstArg := ctx.Args().First() secondArg := ctx.Args().Last() firstURL := getAliasURL(firstArg, config.Aliases) secondURL := getAliasURL(secondArg, config.Aliases) newFirstURL := stripRecursiveURL(firstURL) for diff := range doDiffMain(newFirstURL, secondURL, isURLRecursive(firstURL)) { if diff.Error != nil { // Print in new line and adjust to top so that we don't print over the ongoing scan bar if !globalQuietFlag && !globalJSONFlag { console.Eraseline() } } fatalIf(diff.Error.Trace(newFirstURL, secondURL), "Failed to diff ‘"+firstURL+"’ and ‘"+secondURL+"’.") printMsg(diff) } // Print in new line and adjust to top so that we don't print over the ongoing scan bar if !globalQuietFlag && !globalJSONFlag { console.Eraseline() } console.Println(console.Colorize("DiffMessage", "Done.")) }
// mainCopy is the entry point for cp command. func mainCopy(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'copy' cli arguments. checkCopySyntax(ctx) // Additional command speific theme customization. console.SetColor("Copy", color.New(color.FgGreen, color.Bold)) session := newSessionV6() session.Header.CommandType = "cp" session.Header.CommandBoolFlags["recursive"] = ctx.Bool("recursive") var e error if session.Header.RootPath, e = os.Getwd(); e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. session.Header.CommandArgs = ctx.Args() doCopySession(session) session.Delete() }
// mainDiff - is a handler for mc diff command func mainDiff(ctx *cli.Context) { checkDiffSyntax(ctx) console.SetCustomTheme(map[string]*color.Color{ "DiffMessage": color.New(color.FgGreen, color.Bold), "DiffOnlyInFirst": color.New(color.FgRed, color.Bold), "DiffType": color.New(color.FgYellow, color.Bold), "DiffSize": color.New(color.FgMagenta, color.Bold), }) config := mustGetMcConfig() firstArg := ctx.Args().First() secondArg := ctx.Args().Last() firstURL, err := getCanonicalizedURL(firstArg, config.Aliases) fatalIf(err.Trace(firstArg), "Unable to parse first argument ‘"+firstArg+"’.") secondURL, err := getCanonicalizedURL(secondArg, config.Aliases) fatalIf(err.Trace(secondArg), "Unable to parse second argument ‘"+secondArg+"’.") newFirstURL := stripRecursiveURL(firstURL) for diff := range doDiffCmd(newFirstURL, secondURL, isURLRecursive(firstURL)) { fatalIf(diff.Error.Trace(newFirstURL, secondURL), "Failed to diff ‘"+firstURL+"’ and ‘"+secondURL+"’.") console.Println(diff.String()) } }
func mainConfigVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } config, err := newConfig() fatalIf(err.Trace(), "Failed to initialize ‘quick’ configuration data structure.") configPath := mustGetMcConfigPath() err = config.Load(configPath) fatalIf(err.Trace(configPath), "Unable to load config path") // convert interface{} back to its original struct newConf := config.Data().(*configV5) type Version struct { Value string `json:"value"` } if globalJSONFlag { tB, e := json.Marshal( struct { Version Version `json:"version"` }{Version: Version{newConf.Version}}, ) fatalIf(probe.NewError(e), "Unable to construct version string.") console.Println(string(tB)) return } console.Println(newConf.Version) }
func runMinioInstall(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "install", 1) // last argument is exit code } minioGenerate := command{exec.Command("godep", "go", "generate", "./..."), &bytes.Buffer{}, &bytes.Buffer{}} minioBuild := command{exec.Command("godep", "go", "build", "-a", "./..."), &bytes.Buffer{}, &bytes.Buffer{}} minioTest := command{exec.Command("godep", "go", "test", "-race", "./..."), &bytes.Buffer{}, &bytes.Buffer{}} minioInstall := command{exec.Command("godep", "go", "install", "-a", "github.com/minio/minio"), &bytes.Buffer{}, &bytes.Buffer{}} minioGenerateErr := minioGenerate.runCommand() if minioGenerateErr != nil { fmt.Print(minioGenerate) os.Exit(1) } fmt.Print(minioGenerate) minioBuildErr := minioBuild.runCommand() if minioBuildErr != nil { fmt.Print(minioBuild) os.Exit(1) } fmt.Print(minioBuild) minioTestErr := minioTest.runCommand() if minioTestErr != nil { fmt.Println(minioTest) os.Exit(1) } fmt.Print(minioTest) minioInstallErr := minioInstall.runCommand() if minioInstallErr != nil { fmt.Println(minioInstall) os.Exit(1) } fmt.Print(minioInstall) }
func registerBefore(ctx *cli.Context) error { setMcConfigDir(ctx.GlobalString("config-folder")) globalQuietFlag = ctx.GlobalBool("quiet") globalMimicFlag = ctx.GlobalBool("mimic") globalDebugFlag = ctx.GlobalBool("debug") globalJSONFlag = ctx.GlobalBool("json") if globalDebugFlag { console.NoDebugPrint = false } // Disable color themes. if ctx.GlobalBool("no-color") == true { console.SetColorOff() } // Verify golang runtime verifyMCRuntime() // Migrate any old version of config / state files to newer format. migrate() // Checkconfig if it can be read checkConfig() return nil }
// "minio control lock" entry point. func lockControl(c *cli.Context) { if len(c.Args()) != 1 { cli.ShowCommandHelpAndExit(c, "lock", 1) } parsedURL, err := url.Parse(c.Args()[0]) fatalIf(err, "Unable to parse URL.") authCfg := &authConfig{ accessKey: serverConfig.GetCredential().AccessKeyID, secretKey: serverConfig.GetCredential().SecretAccessKey, address: parsedURL.Host, path: path.Join(reservedBucket, controlPath), loginMethod: "Controller.LoginHandler", } client := newAuthClient(authCfg) args := &GenericArgs{} reply := &SystemLockState{} err = client.Call("Controller.LockInfo", args, reply) // logs the error and returns if err != nil. fatalIf(err, "RPC Controller.LockInfo call failed") // print the lock info on the console. b, err := json.MarshalIndent(*reply, "", " ") fatalIf(err, "Failed to parse the RPC lock info response") fmt.Print(string(b)) }
func mainMirror(ctx *cli.Context) { checkMirrorSyntax(ctx) console.SetCustomTheme(map[string]*color.Color{ "Mirror": color.New(color.FgGreen, color.Bold), }) var e error session := newSessionV2() session.Header.CommandType = "mirror" session.Header.RootPath, e = os.Getwd() if e != nil { session.Close() session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Close() session.Delete() fatalIf(err.Trace(ctx.Args()...), fmt.Sprintf("One or more unknown argument types found in ‘%s’.", ctx.Args())) } doMirrorCmdSession(session) session.Close() session.Delete() }