types.go 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  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. func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  24. if len(args) == 0 {
  25. args = []string{"help"}
  26. }
  27. flags := flag.NewFlagSet("main", flag.ContinueOnError)
  28. flags.SetOutput(stdout)
  29. flags.Usage = func() { stdout.Write([]byte(service.Help())) }
  30. if err := flags.Parse(args); err != nil {
  31. return err
  32. }
  33. cmd := flags.Arg(0)
  34. log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
  35. if cmd == "" {
  36. cmd = "help"
  37. }
  38. method := getMethod(service, cmd)
  39. if method != nil {
  40. return method(stdin, stdout, flags.Args()[1:]...)
  41. }
  42. return errors.New("No such command: " + cmd)
  43. }
  44. func getMethod(service Service, name string) Cmd {
  45. if name == "help" {
  46. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  47. if len(args) == 0 {
  48. stdout.Write([]byte(service.Help()))
  49. } else {
  50. if method := getMethod(service, args[0]); method == nil {
  51. return errors.New("No such command: " + args[0])
  52. } else {
  53. method(stdin, stdout, "--help")
  54. }
  55. }
  56. return nil
  57. }
  58. }
  59. methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
  60. method, exists := reflect.TypeOf(service).MethodByName(methodName)
  61. if !exists {
  62. return nil
  63. }
  64. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  65. ret := method.Func.CallSlice([]reflect.Value{
  66. reflect.ValueOf(service),
  67. reflect.ValueOf(stdin),
  68. reflect.ValueOf(stdout),
  69. reflect.ValueOf(args),
  70. })[0].Interface()
  71. if ret == nil {
  72. return nil
  73. }
  74. return ret.(error)
  75. }
  76. }
  77. func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
  78. flags := flag.NewFlagSet(name, flag.ContinueOnError)
  79. flags.SetOutput(output)
  80. flags.Usage = func() {
  81. fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
  82. flags.PrintDefaults()
  83. }
  84. return flags
  85. }