You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							193 lines
						
					
					
						
							4.1 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							193 lines
						
					
					
						
							4.1 KiB
						
					
					
				| package shell | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"io" | |
| 	"math/rand/v2" | |
| 	"os" | |
| 	"path" | |
| 	"regexp" | |
| 	"slices" | |
| 	"strings" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/cluster" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/master_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/util" | |
| 	"github.com/seaweedfs/seaweedfs/weed/util/grace" | |
| 
 | |
| 	"github.com/peterh/liner" | |
| ) | |
| 
 | |
| var ( | |
| 	line        *liner.State | |
| 	historyPath = path.Join(os.TempDir(), "weed-shell") | |
| ) | |
| 
 | |
| func RunShell(options ShellOptions) { | |
| 	slices.SortFunc(Commands, func(a, b command) int { | |
| 		return strings.Compare(a.Name(), b.Name()) | |
| 	}) | |
| 	line = liner.NewLiner() | |
| 	defer line.Close() | |
| 	grace.OnInterrupt(func() { | |
| 		line.Close() | |
| 	}) | |
| 
 | |
| 	line.SetCtrlCAborts(true) | |
| 	line.SetTabCompletionStyle(liner.TabPrints) | |
| 
 | |
| 	setCompletionHandler() | |
| 	loadHistory() | |
| 
 | |
| 	defer saveHistory() | |
| 
 | |
| 	reg, _ := regexp.Compile(`'.*?'|".*?"|\S+`) | |
| 
 | |
| 	commandEnv := NewCommandEnv(&options) | |
| 
 | |
| 	ctx := context.Background() | |
| 	go commandEnv.MasterClient.KeepConnectedToMaster(ctx) | |
| 	commandEnv.MasterClient.WaitUntilConnected(ctx) | |
| 
 | |
| 	if commandEnv.option.FilerAddress == "" { | |
| 		var filers []pb.ServerAddress | |
| 		commandEnv.MasterClient.WithClient(false, func(client master_pb.SeaweedClient) error { | |
| 			resp, err := client.ListClusterNodes(context.Background(), &master_pb.ListClusterNodesRequest{ | |
| 				ClientType: cluster.FilerType, | |
| 				FilerGroup: *options.FilerGroup, | |
| 			}) | |
| 			if err != nil { | |
| 				return err | |
| 			} | |
| 
 | |
| 			for _, clusterNode := range resp.ClusterNodes { | |
| 				filers = append(filers, pb.ServerAddress(clusterNode.Address)) | |
| 			} | |
| 			return nil | |
| 		}) | |
| 		fmt.Printf("master: %s ", *options.Masters) | |
| 		if len(filers) > 0 { | |
| 			fmt.Printf("filers: %v", filers) | |
| 			commandEnv.option.FilerAddress = filers[rand.IntN(len(filers))] | |
| 		} | |
| 		fmt.Println() | |
| 	} | |
| 
 | |
| 	for { | |
| 		cmd, err := line.Prompt("> ") | |
| 		if err != nil { | |
| 			if err != io.EOF { | |
| 				fmt.Printf("%v\n", err) | |
| 			} | |
| 			return | |
| 		} | |
| 
 | |
| 		if strings.TrimSpace(cmd) != "" { | |
| 			line.AppendHistory(cmd) | |
| 		} | |
| 
 | |
| 		for _, c := range util.StringSplit(cmd, ";") { | |
| 			if processEachCmd(reg, c, commandEnv) { | |
| 				return | |
| 			} | |
| 		} | |
| 	} | |
| } | |
| 
 | |
| func processEachCmd(reg *regexp.Regexp, cmd string, commandEnv *CommandEnv) bool { | |
| 	cmds := reg.FindAllString(cmd, -1) | |
| 
 | |
| 	if len(cmds) == 0 { | |
| 		return false | |
| 	} else { | |
| 
 | |
| 		args := make([]string, len(cmds[1:])) | |
| 
 | |
| 		for i := range args { | |
| 			args[i] = strings.Trim(string(cmds[1+i]), "\"'") | |
| 		} | |
| 
 | |
| 		cmd := cmds[0] | |
| 		if cmd == "help" || cmd == "?" { | |
| 			printHelp(cmds) | |
| 		} else if cmd == "exit" || cmd == "quit" { | |
| 			return true | |
| 		} else { | |
| 			foundCommand := false | |
| 			for _, c := range Commands { | |
| 				if c.Name() == cmd || c.Name() == "fs."+cmd { | |
| 					if err := c.Do(args, commandEnv, os.Stdout); err != nil { | |
| 						fmt.Fprintf(os.Stderr, "error: %v\n", err) | |
| 					} | |
| 					foundCommand = true | |
| 				} | |
| 			} | |
| 			if !foundCommand { | |
| 				fmt.Fprintf(os.Stderr, "unknown command: %v\n", cmd) | |
| 			} | |
| 		} | |
| 
 | |
| 	} | |
| 	return false | |
| } | |
| 
 | |
| func printGenericHelp() { | |
| 	msg := | |
| 		`Type:	"help <command>" for help on <command>. Most commands support "<command> -h" also for options.  | |
| ` | |
| 	fmt.Print(msg) | |
| 
 | |
| 	for _, c := range Commands { | |
| 		helpTexts := strings.SplitN(c.Help(), "\n", 2) | |
| 		fmt.Printf("  %-30s\t# %s \n", c.Name(), helpTexts[0]) | |
| 	} | |
| } | |
| 
 | |
| func printHelp(cmds []string) { | |
| 	args := cmds[1:] | |
| 	if len(args) == 0 { | |
| 		printGenericHelp() | |
| 	} else if len(args) > 1 { | |
| 		fmt.Println() | |
| 	} else { | |
| 		cmd := strings.ToLower(args[0]) | |
| 
 | |
| 		for _, c := range Commands { | |
| 			if strings.ToLower(c.Name()) == cmd { | |
| 				fmt.Printf("  %s\t# %s\n", c.Name(), c.Help()) | |
| 				fmt.Printf("use \"%s -h\" for more details\n", c.Name()) | |
| 			} | |
| 		} | |
| 	} | |
| } | |
| 
 | |
| func setCompletionHandler() { | |
| 	line.SetCompleter(func(line string) (c []string) { | |
| 		for _, i := range Commands { | |
| 			if strings.HasPrefix(i.Name(), strings.ToLower(line)) { | |
| 				c = append(c, i.Name()) | |
| 			} | |
| 		} | |
| 		return | |
| 	}) | |
| } | |
| 
 | |
| func loadHistory() { | |
| 	if f, err := os.Open(historyPath); err == nil { | |
| 		line.ReadHistory(f) | |
| 		f.Close() | |
| 	} | |
| } | |
| 
 | |
| func saveHistory() { | |
| 	if f, err := os.Create(historyPath); err != nil { | |
| 		fmt.Printf("Error creating history file: %v\n", err) | |
| 	} else { | |
| 		if _, err = line.WriteHistory(f); err != nil { | |
| 			fmt.Printf("Error writing history file: %v\n", err) | |
| 		} | |
| 		f.Close() | |
| 	} | |
| }
 |