codekingpro/portable-devtools
114k
1// Copyright 2023 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5package main6 7import (8 "bytes"9 "encoding/json"10 "errors"11 "fmt"12 "io"13 "sync"14 "time"15)16 17// lockedWriter serializes Write calls to an underlying Writer.18type lockedWriter struct {19 lock sync.Mutex20 w io.Writer21}22 23func (w *lockedWriter) Write(b []byte) (int, error) {24 w.lock.Lock()25 defer w.lock.Unlock()26 return w.w.Write(b)27}28 29// testJSONFilter is an io.Writer filter that replaces the Package field in30// test2json output.31type testJSONFilter struct {32 w io.Writer // Underlying writer33 variant string // Add ":variant" to Package field34 35 lineBuf bytes.Buffer // Buffer for incomplete lines36}37 38func (f *testJSONFilter) Write(b []byte) (int, error) {39 bn := len(b)40 41 // Process complete lines, and buffer any incomplete lines.42 for len(b) > 0 {43 nl := bytes.IndexByte(b, '\n')44 if nl < 0 {45 f.lineBuf.Write(b)46 break47 }48 var line []byte49 if f.lineBuf.Len() > 0 {50 // We have buffered data. Add the rest of the line from b and51 // process the complete line.52 f.lineBuf.Write(b[:nl+1])53 line = f.lineBuf.Bytes()54 } else {55 // Process a complete line from b.56 line = b[:nl+1]57 }58 b = b[nl+1:]59 f.process(line)60 f.lineBuf.Reset()61 }62 63 return bn, nil64}65 66func (f *testJSONFilter) Flush() {67 // Write any remaining partial line to the underlying writer.68 if f.lineBuf.Len() > 0 {69 f.w.Write(f.lineBuf.Bytes())70 f.lineBuf.Reset()71 }72}73 74func (f *testJSONFilter) process(line []byte) {75 if len(line) > 0 && line[0] == '{' {76 // Plausible test2json output. Parse it generically.77 //78 // We go to some effort here to preserve key order while doing this79 // generically. This will stay robust to changes in the test2json80 // struct, or other additions outside of it. If humans are ever looking81 // at the output, it's really nice to keep field order because it82 // preserves a lot of regularity in the output.83 dec := json.NewDecoder(bytes.NewBuffer(line))84 dec.UseNumber()85 val, err := decodeJSONValue(dec)86 if err == nil && val.atom == json.Delim('{') {87 // Rewrite the Package field.88 found := false89 for i := 0; i < len(val.seq); i += 2 {90 if val.seq[i].atom == "Package" {91 if pkg, ok := val.seq[i+1].atom.(string); ok {92 val.seq[i+1].atom = pkg + ":" + f.variant93 found = true94 break95 }96 }97 }98 if found {99 data, err := json.Marshal(val)100 if err != nil {101 // Should never happen.102 panic(fmt.Sprintf("failed to round-trip JSON %q: %s", line, err))103 }104 f.w.Write(data)105 // Copy any trailing text. We expect at most a "\n" here, but106 // there could be other text and we want to feed that through.107 io.Copy(f.w, dec.Buffered())108 return109 }110 }111 }112 113 // Something went wrong. Just pass the line through.114 f.w.Write(line)115}116 117type jsonValue struct {118 atom json.Token // If json.Delim, then seq will be filled119 seq []jsonValue // If atom == json.Delim('{'), alternating pairs120}121 122var jsonPop = errors.New("end of JSON sequence")123 124func decodeJSONValue(dec *json.Decoder) (jsonValue, error) {125 t, err := dec.Token()126 if err != nil {127 if err == io.EOF {128 err = io.ErrUnexpectedEOF129 }130 return jsonValue{}, err131 }132 133 switch t := t.(type) {134 case json.Delim:135 if t == '}' || t == ']' {136 return jsonValue{}, jsonPop137 }138 139 var seq []jsonValue140 for {141 val, err := decodeJSONValue(dec)142 if err == jsonPop {143 break144 } else if err != nil {145 return jsonValue{}, err146 }147 seq = append(seq, val)148 }149 return jsonValue{t, seq}, nil150 default:151 return jsonValue{t, nil}, nil152 }153}154 155func (v jsonValue) MarshalJSON() ([]byte, error) {156 var buf bytes.Buffer157 var marshal1 func(v jsonValue) error158 marshal1 = func(v jsonValue) error {159 if t, ok := v.atom.(json.Delim); ok {160 buf.WriteRune(rune(t))161 for i, v2 := range v.seq {162 if t == '{' && i%2 == 1 {163 buf.WriteByte(':')164 } else if i > 0 {165 buf.WriteByte(',')166 }167 if err := marshal1(v2); err != nil {168 return err169 }170 }171 if t == '{' {172 buf.WriteByte('}')173 } else {174 buf.WriteByte(']')175 }176 return nil177 }178 bytes, err := json.Marshal(v.atom)179 if err != nil {180 return err181 }182 buf.Write(bytes)183 return nil184 }185 err := marshal1(v)186 return buf.Bytes(), err187}188 189func synthesizeSkipEvent(enc *json.Encoder, pkg, msg string) {190 type event struct {191 Time time.Time192 Action string193 Package string194 Output string `json:",omitempty"`195 }196 ev := event{Time: time.Now(), Package: pkg, Action: "start"}197 enc.Encode(ev)198 ev.Action = "output"199 ev.Output = msg200 enc.Encode(ev)201 ev.Action = "skip"202 ev.Output = ""203 enc.Encode(ev)204}205 