Index: upgrade/msg.go |
=== added file 'upgrade/msg.go' |
--- upgrade/msg.go 1970-01-01 00:00:00 +0000 |
+++ upgrade/msg.go 2012-06-11 17:51:30 +0000 |
@@ -0,0 +1,125 @@ |
+package upgrade |
+ |
+import ( |
+ "bytes" |
+ "errors" |
+ "fmt" |
+ "io" |
+) |
+ |
+// ErrorMessage is an internal type but exported because it is |
+// cross package. |
+// |
+// ErrorMessage represents a message holding an error. |
+type ErrorMessage struct { |
+ S string |
+} |
+ |
+func (e *ErrorMessage) Error() string { |
+ return e.S |
+} |
+ |
+// ReadMsg is an internal function but exported because it is |
+// cross-package; part of the implementation of the "upgrader" command. |
+// |
+// ReadMsg reads a message from r. If the message type is "error", |
+// a *ErrorMessage value will be returned in err, otherwise if expect |
+// is non-empty, it must match the message type. |
+func ReadMsg(r io.Reader, expect string) (msg []string, err error) { |
+ line, err := readLine(r) |
+ if err != nil { |
+ if err == io.EOF { |
+ err = io.ErrUnexpectedEOF |
+ } |
+ return nil, err |
+ } |
+ if !bytes.HasPrefix(line, []byte("upgrade: ")) { |
+ return nil, fmt.Errorf("bad message %q", line) |
+ } |
+ line = line[len("upgrade:"):] |
+ var m quotedList |
+ if _, err := fmt.Fscanf(bytes.NewReader(line), "%q", &m); err != nil { |
+ return nil, fmt.Errorf("cannot parse message %q: %v", line, err) |
+ } |
+ switch { |
+ case len(m) == 0: |
+ return nil, errors.New("message has no content") |
+ case m[0] == "error" && len(m) < 2: |
+ return nil, errors.New("error with no message") |
+ case m[0] == "error": |
+ return nil, &ErrorMessage{m[1]} |
+ case expect != "" && m[0] != expect: |
+ return nil, fmt.Errorf("unexpected message; expected %q got %q", expect, m) |
+ } |
+ return m, nil |
+} |
+ |
+// WriteMsg is an internal function but exported because it is |
+// cross-package; part of the implementation of the "upgrader" command. |
+// |
+// WriteMsg writes the given message to w. |
+// The message type is given in the first element of m. |
+func WriteMsg(w io.Writer, m ...string) error { |
+ _, err := fmt.Fprintf(w, "upgrade: %q\n", m) |
+ return err |
+} |
+ |
+// readLine reads a full line a byte at a time from the reader |
+// so that we can avoid reading more than we need. |
+func readLine(r io.Reader) (line []byte, err error) { |
+ buf := make([]byte, 1) |
+ for { |
+ n, err := r.Read(buf) |
+ if err != nil { |
+ return nil, err |
+ } |
+ if n == 0 { |
+ continue |
+ } |
+ if buf[0] == '\n' { |
+ return line, nil |
+ } |
+ line = append(line, buf...) |
+ } |
+ panic("not reached") |
+} |
+ |
+// quotedList implements scanning of a quoted string list, as |
+// produced by fmt.Printf("%q", []string{...}) |
+type quotedList []string |
+ |
+func (q *quotedList) Scan(state fmt.ScanState, verb rune) error { |
+ if verb != 'q' && verb != 'v' { |
+ return errors.New("quoted list requires %q or %v verb") |
+ } |
+ state.SkipSpace() |
+ r, _, err := state.ReadRune() |
+ if err != nil { |
+ return err |
+ } |
+ if r != '[' { |
+ state.UnreadRune() |
+ return fmt.Errorf("expected '[', found %#c", r) |
+ } |
+ var s string |
+ for { |
+ state.SkipSpace() |
+ r, _, err = state.ReadRune() |
+ if err != nil { |
+ return err |
+ } |
+ if r == ']' { |
+ return nil |
+ } |
+ state.UnreadRune() |
+ if r != '"' { |
+ return fmt.Errorf("expected ']' or '\"', found %#c", r) |
+ } |
+ _, err := fmt.Fscanf(state, "%q", &s) |
+ if err != nil { |
+ return err |
+ } |
+ *q = append(*q, s) |
+ } |
+ panic("not reached") |
+} |