shell_liner.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package shell
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/seaweedfs/seaweedfs/weed/cluster"
  6. "github.com/seaweedfs/seaweedfs/weed/pb"
  7. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  8. "github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
  9. "github.com/seaweedfs/seaweedfs/weed/util"
  10. "github.com/seaweedfs/seaweedfs/weed/util/grace"
  11. "golang.org/x/exp/slices"
  12. "io"
  13. "math/rand"
  14. "os"
  15. "path"
  16. "regexp"
  17. "strings"
  18. "github.com/peterh/liner"
  19. )
  20. var (
  21. line *liner.State
  22. historyPath = path.Join(os.TempDir(), "weed-shell")
  23. )
  24. func RunShell(options ShellOptions) {
  25. slices.SortFunc(Commands, func(a, b command) bool {
  26. return strings.Compare(a.Name(), b.Name()) < 0
  27. })
  28. line = liner.NewLiner()
  29. defer line.Close()
  30. grace.OnInterrupt(func() {
  31. line.Close()
  32. })
  33. line.SetCtrlCAborts(true)
  34. line.SetTabCompletionStyle(liner.TabPrints)
  35. setCompletionHandler()
  36. loadHistory()
  37. defer saveHistory()
  38. reg, _ := regexp.Compile(`'.*?'|".*?"|\S+`)
  39. commandEnv := NewCommandEnv(&options)
  40. go commandEnv.MasterClient.KeepConnectedToMaster()
  41. commandEnv.MasterClient.WaitUntilConnected()
  42. if commandEnv.option.FilerAddress == "" {
  43. var filers []pb.ServerAddress
  44. commandEnv.MasterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
  45. resp, err := client.ListClusterNodes(context.Background(), &master_pb.ListClusterNodesRequest{
  46. ClientType: cluster.FilerType,
  47. FilerGroup: *options.FilerGroup,
  48. })
  49. if err != nil {
  50. return err
  51. }
  52. for _, clusterNode := range resp.ClusterNodes {
  53. filers = append(filers, pb.ServerAddress(clusterNode.Address))
  54. }
  55. return nil
  56. })
  57. fmt.Printf("master: %s ", *options.Masters)
  58. if len(filers) > 0 {
  59. fmt.Printf("filers: %v", filers)
  60. commandEnv.option.FilerAddress = filers[rand.Intn(len(filers))]
  61. }
  62. fmt.Println()
  63. }
  64. if commandEnv.option.FilerAddress != "" {
  65. commandEnv.WithFilerClient(false, func(filerClient filer_pb.SeaweedFilerClient) error {
  66. resp, err := filerClient.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
  67. if err != nil {
  68. return err
  69. }
  70. if resp.ClusterId != "" {
  71. fmt.Printf(`
  72. ---
  73. Free Monitoring Data URL:
  74. https://cloud.seaweedfs.com/ui/%s
  75. ---
  76. `, resp.ClusterId)
  77. }
  78. return nil
  79. })
  80. }
  81. for {
  82. cmd, err := line.Prompt("> ")
  83. if err != nil {
  84. if err != io.EOF {
  85. fmt.Printf("%v\n", err)
  86. }
  87. return
  88. }
  89. for _, c := range util.StringSplit(cmd, ";") {
  90. if processEachCmd(reg, c, commandEnv) {
  91. return
  92. }
  93. }
  94. }
  95. }
  96. func processEachCmd(reg *regexp.Regexp, cmd string, commandEnv *CommandEnv) bool {
  97. cmds := reg.FindAllString(cmd, -1)
  98. line.AppendHistory(cmd)
  99. if len(cmds) == 0 {
  100. return false
  101. } else {
  102. args := make([]string, len(cmds[1:]))
  103. for i := range args {
  104. args[i] = strings.Trim(string(cmds[1+i]), "\"'")
  105. }
  106. cmd := cmds[0]
  107. if cmd == "help" || cmd == "?" {
  108. printHelp(cmds)
  109. } else if cmd == "exit" || cmd == "quit" {
  110. return true
  111. } else {
  112. foundCommand := false
  113. for _, c := range Commands {
  114. if c.Name() == cmd || c.Name() == "fs."+cmd {
  115. if err := c.Do(args, commandEnv, os.Stdout); err != nil {
  116. fmt.Fprintf(os.Stderr, "error: %v\n", err)
  117. }
  118. foundCommand = true
  119. }
  120. }
  121. if !foundCommand {
  122. fmt.Fprintf(os.Stderr, "unknown command: %v\n", cmd)
  123. }
  124. }
  125. }
  126. return false
  127. }
  128. func printGenericHelp() {
  129. msg :=
  130. `Type: "help <command>" for help on <command>. Most commands support "<command> -h" also for options.
  131. `
  132. fmt.Print(msg)
  133. for _, c := range Commands {
  134. helpTexts := strings.SplitN(c.Help(), "\n", 2)
  135. fmt.Printf(" %-30s\t# %s \n", c.Name(), helpTexts[0])
  136. }
  137. }
  138. func printHelp(cmds []string) {
  139. args := cmds[1:]
  140. if len(args) == 0 {
  141. printGenericHelp()
  142. } else if len(args) > 1 {
  143. fmt.Println()
  144. } else {
  145. cmd := strings.ToLower(args[0])
  146. for _, c := range Commands {
  147. if c.Name() == cmd {
  148. fmt.Printf(" %s\t# %s\n", c.Name(), c.Help())
  149. }
  150. }
  151. }
  152. }
  153. func setCompletionHandler() {
  154. line.SetCompleter(func(line string) (c []string) {
  155. for _, i := range Commands {
  156. if strings.HasPrefix(i.Name(), strings.ToLower(line)) {
  157. c = append(c, i.Name())
  158. }
  159. }
  160. return
  161. })
  162. }
  163. func loadHistory() {
  164. if f, err := os.Open(historyPath); err == nil {
  165. line.ReadHistory(f)
  166. f.Close()
  167. }
  168. }
  169. func saveHistory() {
  170. if f, err := os.Create(historyPath); err != nil {
  171. fmt.Printf("Error creating history file: %v\n", err)
  172. } else {
  173. if _, err = line.WriteHistory(f); err != nil {
  174. fmt.Printf("Error writing history file: %v\n", err)
  175. }
  176. f.Close()
  177. }
  178. }