types.go 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package rcli
  2. // rcli (Remote Command-Line Interface) is a simple protocol for...
  3. // serving command-line interfaces remotely.
  4. //
  5. // rcli can be used over any transport capable of a) sending binary streams in
  6. // both directions, and b) capable of half-closing a connection. TCP and Unix sockets
  7. // are the usual suspects.
  8. import (
  9. "errors"
  10. "flag"
  11. "fmt"
  12. "io"
  13. "log"
  14. "reflect"
  15. "strings"
  16. )
  17. type Service interface {
  18. Name() string
  19. Help() string
  20. }
  21. type Cmd func(io.ReadCloser, io.Writer, ...string) error
  22. type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
  23. // FIXME: For reverse compatibility
  24. func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  25. return LocalCall(service, stdin, stdout, args...)
  26. }
  27. func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  28. if len(args) == 0 {
  29. args = []string{"help"}
  30. }
  31. flags := flag.NewFlagSet("main", flag.ContinueOnError)
  32. flags.SetOutput(stdout)
  33. flags.Usage = func() { stdout.Write([]byte(service.Help())) }
  34. if err := flags.Parse(args); err != nil {
  35. return err
  36. }
  37. cmd := flags.Arg(0)
  38. log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
  39. if cmd == "" {
  40. cmd = "help"
  41. }
  42. method := getMethod(service, cmd)
  43. if method != nil {
  44. return method(stdin, stdout, flags.Args()[1:]...)
  45. }
  46. return errors.New("No such command: " + cmd)
  47. }
  48. func getMethod(service Service, name string) Cmd {
  49. if name == "help" {
  50. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  51. if len(args) == 0 {
  52. stdout.Write([]byte(service.Help()))
  53. } else {
  54. if method := getMethod(service, args[0]); method == nil {
  55. return errors.New("No such command: " + args[0])
  56. } else {
  57. method(stdin, stdout, "--help")
  58. }
  59. }
  60. return nil
  61. }
  62. }
  63. methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
  64. method, exists := reflect.TypeOf(service).MethodByName(methodName)
  65. if !exists {
  66. return nil
  67. }
  68. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  69. ret := method.Func.CallSlice([]reflect.Value{
  70. reflect.ValueOf(service),
  71. reflect.ValueOf(stdin),
  72. reflect.ValueOf(stdout),
  73. reflect.ValueOf(args),
  74. })[0].Interface()
  75. if ret == nil {
  76. return nil
  77. }
  78. return ret.(error)
  79. }
  80. }
  81. func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
  82. flags := flag.NewFlagSet(name, flag.ContinueOnError)
  83. flags.SetOutput(output)
  84. flags.Usage = func() {
  85. fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
  86. flags.PrintDefaults()
  87. }
  88. return flags
  89. }