From 8be5b95e344b6f0a075ccb21187b2a4dee31a3b8 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Mon, 7 Jun 2021 22:16:37 +0200 Subject: [PATCH 01/15] Collecting documentation on channels & goroutines --- notes/notes.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/notes/notes.md b/notes/notes.md index 3e82725..c9d7fe7 100644 --- a/notes/notes.md +++ b/notes/notes.md @@ -61,5 +61,13 @@ See also [Create a CLI in golang with Cobra](https://codesource.io/create-a-cli- * [Think Differently About What to Log in Go: Best Practices Examined](https://www.loggly.com/blog/think-differently-about-what-to-log-in-go-best-practices-examined/) * [How to Design a Basic Logging System in Your Go Application](https://betterprogramming.pub/understanding-and-designing-logging-system-in-go-application-c85a28bb8526) +## Go Routines and channels +### Stopping +* [Stopping Goroutines](https://medium.com/@matryer/stopping-goroutines-golang-1bf28799c1cb), 2015 +* [How to kill execution of goroutine?](https://www.golangprograms.com/how-to-kill-execution-of-goroutine.html) +* [Go - graceful shutdown of worker goroutines](https://callistaenterprise.se/blogg/teknik/2019/10/05/go-worker-cancellation/), 2019 +* [Stopping goroutines](https://riptutorial.com/go/example/6055/stopping-goroutines) +* [Never start a goroutine without knowing how it will stop](https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop), Dave Cheney, 2016 + ## Miscelaneous * [Buy Me a Coffe badge](https://gist.github.com/gbraad/216f8162d9b382d14b8a097a37cc2c72#file-readme-md) From 982047119b80ef8bbcd341fcbdeeeb3a4efdc52e Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Sun, 13 Jun 2021 21:32:37 +0200 Subject: [PATCH 02/15] Starting to channalize listen --- cmd/root.go | 2 +- pkg/morserino_com/listen_com.go | 23 +++++++------ pkg/morserino_com/listen_com_test.go | 50 +++++++++++++++++++++++----- pkg/morserino_core/core.go | 6 +++- pkg/safebuffer/safebuffer.go | 30 +++++++++++++++++ 5 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 pkg/safebuffer/safebuffer.go diff --git a/cmd/root.go b/cmd/root.go index 0ae03ec..c790468 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,7 +51,7 @@ For easy reading, a new line is inserted after a "=" sign. // Run: func(cmd *cobra.Command, args []string) { }, // Run: func(cmd *cobra.Command, args []string) { // //Displaying on console is de default behaviour - // morserino_com.ConsoleListen(morserinoPortName) + // morserino_com.Listen(morserinoPortName) // }, } diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino_com/listen_com.go index fac3b7f..1b3fa80 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino_com/listen_com.go @@ -29,17 +29,18 @@ import ( "strings" "testing/iotest" - "github.com/on4kjm/morserino_display/pkg/morserino_console" "go.bug.st/serial" ) +const exitString string = "\nExiting...\n" + // Main listen function with display to the console -func ConsoleListen(morserinoPortName string, genericEnumPorts comPortEnumerator) error { +func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, MessageBuffer chan string) error { //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { TestMessage := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" - return Listen(iotest.OneByteReader(strings.NewReader(TestMessage))) + return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), MessageBuffer) } //If portname "auto" was specified, we scan for the Morserino port @@ -67,14 +68,14 @@ func ConsoleListen(morserinoPortName string, genericEnumPorts comPortEnumerator) } defer p.Close() - return Listen(p) + return Listen(p, MessageBuffer) } // Main receive loop -func Listen(port io.Reader) error { +func Listen(port io.Reader, MessageBuffer chan string) error { - //TODO: needs to be moved as a goroutine - consoleDisplay := morserino_console.ConsoleDisplay{} + // //TODO: needs to be moved as a goroutine + // consoleDisplay := morserino_console.ConsoleDisplay{} // variables for tracking the exit pattern var ( @@ -114,11 +115,11 @@ func Listen(port io.Reader) error { break } - // TODO: move this out and use a channel for that - consoleDisplay.Add(string(buff[:n])) + MessageBuffer <- string(buff[:n]) if closeRequested { - consoleDisplay.Add("\nExiting...\n") + //FIXME: This shoudl be a constant + MessageBuffer <- exitString break } } @@ -126,7 +127,7 @@ func Listen(port io.Reader) error { return nil } -// Tries to auto detect the Morserino port +// Tries to auto detect the Morserino port based on the USB Vendor and Device ID func DetectDevice(genericEnumPorts comPortEnumerator) (string, error) { theComPortList, err := Get_com_list(genericEnumPorts) if err != nil { diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino_com/listen_com_test.go index 80316dd..8e6aed0 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino_com/listen_com_test.go @@ -2,10 +2,12 @@ package morserino_com import ( "fmt" + "log" "strings" "testing" "testing/iotest" + "github.com/on4kjm/morserino_display/pkg/safebuffer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.bug.st/serial/enumerator" @@ -77,47 +79,79 @@ func TestDetectDevice_PortEnumerationWentWrong(t *testing.T) { func TestListen_HappyCase(t *testing.T) { // Given - mock := iotest.OneByteReader(strings.NewReader("Test = test e e")) + testMsg := "Test = test e e" + mock := iotest.OneByteReader(strings.NewReader(testMsg)) + var MessageBuffer = make(chan string, 10) + var testBuffer safebuffer.Buffer // When - err := Listen(mock) + // Starts listener function so that we can check what has been actually received + go MockListener(MessageBuffer, &testBuffer) + err := Listen(mock, MessageBuffer) // Then require.NoError(t, err) + assert.Equal(t, testBuffer.String(), testMsg + exitString) } func TestListen_missedEndMarker(t *testing.T) { // Given - mock := iotest.OneByteReader(strings.NewReader("Test = test e e")) + testMsg := "Test = test e e" + mock := iotest.OneByteReader(strings.NewReader(testMsg)) + var MessageBuffer = make(chan string, 10) + var testBuffer safebuffer.Buffer + // When - err := Listen(mock) + // Starts listener function so that we can check what has been actually received + go MockListener(MessageBuffer, &testBuffer) + err := Listen(mock, MessageBuffer) // Then require.NoError(t, err) + assert.Equal(t, testMsg + exitString, testBuffer.String()) } //EOF error (no error but no data returned) func TestListen_EOF(t *testing.T) { // Given mock := iotest.ErrReader(nil) + var MessageBuffer = make(chan string, 10) // When - err := Listen(mock) + err := Listen(mock, MessageBuffer) // Then require.NoError(t, err) } // -// Test ConsoleListen() +// Test Listen() // -func TestConsoleListen_withSimulator(t *testing.T) { +func TestListen_withSimulator(t *testing.T) { // Given + var MessageBuffer = make(chan string, 10) // When - err := ConsoleListen("simul", nil) + err := OpenAndListen("simul", nil, MessageBuffer) // Then require.NoError(t, err) } + +func MockListener(MessageBuffer chan string, workBuffer *safebuffer.Buffer) { + + for { + var output string + output = <-MessageBuffer + _, err := workBuffer.Write([]byte(output)) + if err != nil { + log.Fatal(err) + } + + if strings.Contains(output, "\nExiting...\n") { + return + } + } + +} diff --git a/pkg/morserino_core/core.go b/pkg/morserino_core/core.go index f9eac93..84b8aa7 100644 --- a/pkg/morserino_core/core.go +++ b/pkg/morserino_core/core.go @@ -31,10 +31,14 @@ import ( // Main entry point for console output func Morserino_console(morserinoPortName string) { + + // String channel used to communicate with the display routines + var MessageBuffer = make(chan string, 10) + // Setting up the EnumPorts to the "real life" implementation var realEnumPorts morserino_com.EnumeratePorts - morserino_com.ConsoleListen(morserinoPortName, realEnumPorts) + morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, MessageBuffer) } //Main entry point for listing ports diff --git a/pkg/safebuffer/safebuffer.go b/pkg/safebuffer/safebuffer.go new file mode 100644 index 0000000..e825c98 --- /dev/null +++ b/pkg/safebuffer/safebuffer.go @@ -0,0 +1,30 @@ +package safebuffer + +// from https://gist.github.com/arkan/5924e155dbb4254b64614069ba0afd81 + +import ( + "bytes" + "sync" +) + +// Buffer is a goroutine safe bytes.Buffer +type Buffer struct { + buffer bytes.Buffer + mutex sync.Mutex +} + +// Write appends the contents of p to the buffer, growing the buffer as needed. It returns +// the number of bytes written. +func (s *Buffer) Write(p []byte) (n int, err error) { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.buffer.Write(p) +} + +// String returns the contents of the unread portion of the buffer +// as a string. If the Buffer is a nil pointer, it returns "". +func (s *Buffer) String() string { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.buffer.String() +} From 7966b5aff5bd12f7b470b61c7b13794649b1ea90 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Tue, 15 Jun 2021 21:49:37 +0200 Subject: [PATCH 03/15] WIP --- pkg/morserino_com/listen_com.go | 13 ++++++++----- pkg/morserino_com/listen_com_test.go | 27 ++++++++++++++++++++------- pkg/morserino_core/core.go | 3 ++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino_com/listen_com.go index 1b3fa80..70715fb 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino_com/listen_com.go @@ -35,12 +35,12 @@ import ( const exitString string = "\nExiting...\n" // Main listen function with display to the console -func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, MessageBuffer chan string) error { +func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, MessageBuffer chan string, DisplayCompleted chan bool) error { //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { TestMessage := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" - return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), MessageBuffer) + return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), MessageBuffer, DisplayCompleted) } //If portname "auto" was specified, we scan for the Morserino port @@ -68,11 +68,11 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, } defer p.Close() - return Listen(p, MessageBuffer) + return Listen(p, MessageBuffer, DisplayCompleted) } // Main receive loop -func Listen(port io.Reader, MessageBuffer chan string) error { +func Listen(port io.Reader, MessageBuffer chan string, DisplayCompleted chan bool) error { // //TODO: needs to be moved as a goroutine // consoleDisplay := morserino_console.ConsoleDisplay{} @@ -112,14 +112,17 @@ func Listen(port io.Reader, MessageBuffer chan string) error { if n == 0 { fmt.Println("\nEOF") + //FIXME: handle this although it will never occur in real life break } MessageBuffer <- string(buff[:n]) if closeRequested { - //FIXME: This shoudl be a constant + //sending the exit marker to the diplay goroutine MessageBuffer <- exitString + //waiting for it to complete (blocking read) + <- DisplayCompleted break } } diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino_com/listen_com_test.go index 8e6aed0..a4240ce 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino_com/listen_com_test.go @@ -82,12 +82,13 @@ func TestListen_HappyCase(t *testing.T) { testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) var MessageBuffer = make(chan string, 10) + var DisplayCompleted = make(chan bool) var testBuffer safebuffer.Buffer // When // Starts listener function so that we can check what has been actually received - go MockListener(MessageBuffer, &testBuffer) - err := Listen(mock, MessageBuffer) + go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) + err := Listen(mock, MessageBuffer, DisplayCompleted) // Then require.NoError(t, err) @@ -99,13 +100,14 @@ func TestListen_missedEndMarker(t *testing.T) { testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) var MessageBuffer = make(chan string, 10) + var DisplayCompleted = make(chan bool) var testBuffer safebuffer.Buffer // When // Starts listener function so that we can check what has been actually received - go MockListener(MessageBuffer, &testBuffer) - err := Listen(mock, MessageBuffer) + go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) + err := Listen(mock, MessageBuffer, DisplayCompleted) // Then require.NoError(t, err) @@ -117,9 +119,13 @@ func TestListen_EOF(t *testing.T) { // Given mock := iotest.ErrReader(nil) var MessageBuffer = make(chan string, 10) + var DisplayCompleted = make(chan bool) + + //FIXME: + //DisplayCompleted <- true // When - err := Listen(mock, MessageBuffer) + err := Listen(mock, MessageBuffer, DisplayCompleted) // Then require.NoError(t, err) @@ -130,16 +136,22 @@ func TestListen_EOF(t *testing.T) { // func TestListen_withSimulator(t *testing.T) { // Given + testMsg := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" var MessageBuffer = make(chan string, 10) + var DisplayCompleted = make(chan bool) + var testBuffer safebuffer.Buffer // When - err := OpenAndListen("simul", nil, MessageBuffer) + // Starts listener function so that we can check what has been actually received + go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) + err := OpenAndListen("simul", nil, MessageBuffer, DisplayCompleted) // Then require.NoError(t, err) + assert.Equal(t, testBuffer.String(), testMsg + exitString) } -func MockListener(MessageBuffer chan string, workBuffer *safebuffer.Buffer) { +func MockListener(MessageBuffer chan string, DisplayCompleted chan bool, workBuffer *safebuffer.Buffer) { for { var output string @@ -150,6 +162,7 @@ func MockListener(MessageBuffer chan string, workBuffer *safebuffer.Buffer) { } if strings.Contains(output, "\nExiting...\n") { + DisplayCompleted <- true return } } diff --git a/pkg/morserino_core/core.go b/pkg/morserino_core/core.go index 84b8aa7..be61e69 100644 --- a/pkg/morserino_core/core.go +++ b/pkg/morserino_core/core.go @@ -34,11 +34,12 @@ func Morserino_console(morserinoPortName string) { // String channel used to communicate with the display routines var MessageBuffer = make(chan string, 10) + var DisplayCompleted = make(chan bool) // Setting up the EnumPorts to the "real life" implementation var realEnumPorts morserino_com.EnumeratePorts - morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, MessageBuffer) + morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, MessageBuffer, DisplayCompleted) } //Main entry point for listing ports From 086e9ac601b9347c19a8aa8ab668c070bf398639 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Wed, 16 Jun 2021 21:15:20 +0200 Subject: [PATCH 04/15] WIP --- pkg/morserino_com/listen_com.go | 6 +++++- pkg/morserino_com/listen_com_test.go | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino_com/listen_com.go index 70715fb..0fc9af0 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino_com/listen_com.go @@ -112,7 +112,11 @@ func Listen(port io.Reader, MessageBuffer chan string, DisplayCompleted chan boo if n == 0 { fmt.Println("\nEOF") - //FIXME: handle this although it will never occur in real life + MessageBuffer <- "\nEOF" + //sending the exit marker to the diplay goroutine + MessageBuffer <- exitString + //waiting for it to complete (blocking read) + <- DisplayCompleted break } diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino_com/listen_com_test.go index a4240ce..4e916a9 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino_com/listen_com_test.go @@ -117,18 +117,19 @@ func TestListen_missedEndMarker(t *testing.T) { //EOF error (no error but no data returned) func TestListen_EOF(t *testing.T) { // Given + testMsg := "\nEOF" mock := iotest.ErrReader(nil) var MessageBuffer = make(chan string, 10) var DisplayCompleted = make(chan bool) - - //FIXME: - //DisplayCompleted <- true + var testBuffer safebuffer.Buffer // When + go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) err := Listen(mock, MessageBuffer, DisplayCompleted) // Then require.NoError(t, err) + assert.Equal(t, testMsg + exitString, testBuffer.String()) } // From a8fedb1c4e1d1aa74191cfa10926c199aeb5aca5 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Thu, 17 Jun 2021 21:23:02 +0200 Subject: [PATCH 05/15] move channels in a structure --- go.mod | 4 +- go.sum | 387 +++++++++++++++++- pkg/morserino_channels/channels.go | 43 ++ pkg/morserino_com/listen_com.go | 21 +- pkg/morserino_com/listen_com_test.go | 50 +-- pkg/morserino_console/console_display.go | 27 +- pkg/morserino_console/console_display_test.go | 28 ++ pkg/morserino_core/core.go | 10 +- 8 files changed, 506 insertions(+), 64 deletions(-) create mode 100644 pkg/morserino_channels/channels.go create mode 100644 pkg/morserino_console/console_display_test.go diff --git a/go.mod b/go.mod index f5762bc..7f9ce14 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.16 require ( github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.1.3 - github.com/spf13/viper v1.7.1 + github.com/spf13/viper v1.8.0 github.com/stretchr/testify v1.7.0 go.bug.st/serial v1.1.3 - golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 ) diff --git a/go.sum b/go.sum index 2f874a5..eed2a61 100644 --- a/go.sum +++ b/go.sum @@ -5,18 +5,44 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -24,12 +50,21 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= @@ -39,33 +74,89 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -74,6 +165,7 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -95,25 +187,32 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -126,17 +225,22 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -144,12 +248,14 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -162,55 +268,78 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.0 h1:QRwDgoG8xX+kp69di68D+YYTCWfYEckbZRfUlEIAal0= +github.com/spf13/viper v1.8.0/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.bug.st/serial v1.1.3 h1:YEBxJa9pKS9Wdg46B/jiaKbvvbUrjhZZZITfJHEJhaE= go.bug.st/serial v1.1.3/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk= -go.bug.st/serial.v1 v0.0.0-20191202182710-24a6610f0541 h1:eQfoPfT+gNSh63t/oKanQlZyKgblRa/LMZRPIT+MHzA= -go.bug.st/serial.v1 v0.0.0-20191202182710-24a6610f0541/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -220,10 +349,22 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -238,14 +379,53 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -258,16 +438,53 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -278,6 +495,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -285,16 +503,73 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -304,27 +579,97 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/morserino_channels/channels.go b/pkg/morserino_channels/channels.go new file mode 100644 index 0000000..cefab8f --- /dev/null +++ b/pkg/morserino_channels/channels.go @@ -0,0 +1,43 @@ +package morserino_channels + +/* +Copyright © 2021 Jean-Marc Meessen, ON4KJM + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Structure containing all the channels used by the application +type MorserinoChannels struct { + // Channel for the data flow from serial port to display goroutines + MessageBuffer chan string + + // Channel indicating that all data has been displayed and that the + // application can be closed + DisplayCompleted chan bool + + // Channel used to report back errors in goroutines + Error chan error +} + +//Initialize the channels +func (mc MorserinoChannels) Init() { + mc.MessageBuffer = make(chan string, 10) + mc.DisplayCompleted = make(chan bool) + mc.Error = make(chan error) +} \ No newline at end of file diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino_com/listen_com.go index 0fc9af0..7387e28 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino_com/listen_com.go @@ -29,18 +29,19 @@ import ( "strings" "testing/iotest" + "github.com/on4kjm/morserino_display/pkg/morserino_channels" "go.bug.st/serial" ) const exitString string = "\nExiting...\n" // Main listen function with display to the console -func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, MessageBuffer chan string, DisplayCompleted chan bool) error { +func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels morserino_channels.MorserinoChannels) error { //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { TestMessage := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" - return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), MessageBuffer, DisplayCompleted) + return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), channels) } //If portname "auto" was specified, we scan for the Morserino port @@ -68,11 +69,11 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, } defer p.Close() - return Listen(p, MessageBuffer, DisplayCompleted) + return Listen(p, channels) } // Main receive loop -func Listen(port io.Reader, MessageBuffer chan string, DisplayCompleted chan bool) error { +func Listen(port io.Reader, channels morserino_channels.MorserinoChannels) error { // //TODO: needs to be moved as a goroutine // consoleDisplay := morserino_console.ConsoleDisplay{} @@ -112,21 +113,21 @@ func Listen(port io.Reader, MessageBuffer chan string, DisplayCompleted chan boo if n == 0 { fmt.Println("\nEOF") - MessageBuffer <- "\nEOF" + channels.MessageBuffer <- "\nEOF" //sending the exit marker to the diplay goroutine - MessageBuffer <- exitString + channels.MessageBuffer <- exitString //waiting for it to complete (blocking read) - <- DisplayCompleted + <-channels.DisplayCompleted break } - MessageBuffer <- string(buff[:n]) + channels.MessageBuffer <- string(buff[:n]) if closeRequested { //sending the exit marker to the diplay goroutine - MessageBuffer <- exitString + channels.MessageBuffer <- exitString //waiting for it to complete (blocking read) - <- DisplayCompleted + <-channels.DisplayCompleted break } } diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino_com/listen_com_test.go index 4e916a9..5033321 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino_com/listen_com_test.go @@ -7,6 +7,7 @@ import ( "testing" "testing/iotest" + "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/safebuffer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -81,37 +82,37 @@ func TestListen_HappyCase(t *testing.T) { // Given testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) - var MessageBuffer = make(chan string, 10) - var DisplayCompleted = make(chan bool) var testBuffer safebuffer.Buffer + var mc morserino_channels.MorserinoChannels + mc.Init() + // When // Starts listener function so that we can check what has been actually received - go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) - err := Listen(mock, MessageBuffer, DisplayCompleted) + go MockListener(mc, &testBuffer) + err := Listen(mock, mc) // Then require.NoError(t, err) - assert.Equal(t, testBuffer.String(), testMsg + exitString) + assert.Equal(t, testBuffer.String(), testMsg+exitString) } func TestListen_missedEndMarker(t *testing.T) { // Given testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) - var MessageBuffer = make(chan string, 10) - var DisplayCompleted = make(chan bool) var testBuffer safebuffer.Buffer - + var mc morserino_channels.MorserinoChannels + mc.Init() // When // Starts listener function so that we can check what has been actually received - go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) - err := Listen(mock, MessageBuffer, DisplayCompleted) + go MockListener(mc, &testBuffer) + err := Listen(mock, mc) // Then require.NoError(t, err) - assert.Equal(t, testMsg + exitString, testBuffer.String()) + assert.Equal(t, testMsg+exitString, testBuffer.String()) } //EOF error (no error but no data returned) @@ -119,17 +120,18 @@ func TestListen_EOF(t *testing.T) { // Given testMsg := "\nEOF" mock := iotest.ErrReader(nil) - var MessageBuffer = make(chan string, 10) - var DisplayCompleted = make(chan bool) var testBuffer safebuffer.Buffer + var mc morserino_channels.MorserinoChannels + mc.Init() + // When - go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) - err := Listen(mock, MessageBuffer, DisplayCompleted) + go MockListener(mc, &testBuffer) + err := Listen(mock, mc) // Then require.NoError(t, err) - assert.Equal(t, testMsg + exitString, testBuffer.String()) + assert.Equal(t, testMsg+exitString, testBuffer.String()) } // @@ -138,32 +140,32 @@ func TestListen_EOF(t *testing.T) { func TestListen_withSimulator(t *testing.T) { // Given testMsg := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" - var MessageBuffer = make(chan string, 10) - var DisplayCompleted = make(chan bool) var testBuffer safebuffer.Buffer + var mc morserino_channels.MorserinoChannels + mc.Init() // When // Starts listener function so that we can check what has been actually received - go MockListener(MessageBuffer, DisplayCompleted, &testBuffer) - err := OpenAndListen("simul", nil, MessageBuffer, DisplayCompleted) + go MockListener(mc, &testBuffer) + err := OpenAndListen("simul", nil, mc) // Then require.NoError(t, err) - assert.Equal(t, testBuffer.String(), testMsg + exitString) + assert.Equal(t, testBuffer.String(), testMsg+exitString) } -func MockListener(MessageBuffer chan string, DisplayCompleted chan bool, workBuffer *safebuffer.Buffer) { +func MockListener(mc morserino_channels.MorserinoChannels, workBuffer *safebuffer.Buffer) { for { var output string - output = <-MessageBuffer + output = <- mc.MessageBuffer _, err := workBuffer.Write([]byte(output)) if err != nil { log.Fatal(err) } if strings.Contains(output, "\nExiting...\n") { - DisplayCompleted <- true + mc.DisplayCompleted <- true return } } diff --git a/pkg/morserino_console/console_display.go b/pkg/morserino_console/console_display.go index 60aedfd..c894bbb 100644 --- a/pkg/morserino_console/console_display.go +++ b/pkg/morserino_console/console_display.go @@ -24,26 +24,47 @@ THE SOFTWARE. import ( "fmt" + "io" "strings" + + "github.com/on4kjm/morserino_display/pkg/morserino_channels" ) +func ConsoleDisplayListener(mc morserino_channels.MorserinoChannels) { + var display ConsoleDisplay + for { + var output string + output = <- mc.MessageBuffer + display.Add(output) + + if strings.Contains(output, "\nExiting...\n") { + mc.DisplayCompleted <- true + return + } + } +} + //FIXME: Add comment type ConsoleDisplay struct { - currentLine string + currentLine strings.Builder newLine string + w io.Writer } func (cd *ConsoleDisplay) String() string { //FIXME: add something useful here - return "" + return cd.currentLine.String() } func (cd *ConsoleDisplay) Add(buff string) { if strings.Contains(buff, "=") { - //FIXME: is the buffer one char long + //FIXME: is the buffer one char long? It is generally followed by a space fmt.Println("=") + //FIXME: better string accumulation + cd.currentLine.WriteString("=\n") } else { fmt.Printf("%s", buff) + cd.currentLine.WriteString(buff) } } diff --git a/pkg/morserino_console/console_display_test.go b/pkg/morserino_console/console_display_test.go new file mode 100644 index 0000000..4d27665 --- /dev/null +++ b/pkg/morserino_console/console_display_test.go @@ -0,0 +1,28 @@ +package morserino_console + +import ( + "strings" + "testing" + "testing/iotest" + + "github.com/on4kjm/morserino_display/pkg/morserino_channels" + "github.com/on4kjm/morserino_display/pkg/morserino_com" +) + +func TestConsoleDisplayListener_happyCase(t *testing.T) { + // ** Given + testMsg := "Test = test e e" + var mc morserino_channels.MorserinoChannels + mc.Init() + + // ** When + go serialListenerMock(testMsg, mc) + go ConsoleDisplayListener(mc) + + // ** Then +} + +// A mock to simulaate the serial port listener goroutine +func serialListenerMock(TestString string, mc morserino_channels.MorserinoChannels) { + morserino_com.Listen(iotest.OneByteReader(strings.NewReader(TestString)), mc) +} diff --git a/pkg/morserino_core/core.go b/pkg/morserino_core/core.go index be61e69..bce4bd7 100644 --- a/pkg/morserino_core/core.go +++ b/pkg/morserino_core/core.go @@ -26,20 +26,22 @@ import ( "fmt" "log" + "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/morserino_com" ) // Main entry point for console output func Morserino_console(morserinoPortName string) { - // String channel used to communicate with the display routines - var MessageBuffer = make(chan string, 10) - var DisplayCompleted = make(chan bool) + // initialize the structure containing all the channels we are going to use + var channels morserino_channels.MorserinoChannels + channels.Init() + // Setting up the EnumPorts to the "real life" implementation var realEnumPorts morserino_com.EnumeratePorts - morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, MessageBuffer, DisplayCompleted) + morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, channels) } //Main entry point for listing ports From 8649d094bb2c27ad02ec36523c78952187faf3fd Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Sat, 19 Jun 2021 22:12:49 +0200 Subject: [PATCH 06/15] wip --- pkg/morserino_channels/channels.go | 11 +++++--- pkg/morserino_com/listen_com.go | 5 ++-- pkg/morserino_com/listen_com_test.go | 14 +++++----- pkg/morserino_console/console_display.go | 21 ++++++++++----- pkg/morserino_console/console_display_test.go | 27 ++++++++++++++++--- pkg/morserino_console/testfile.txt | 0 pkg/morserino_core/core.go | 3 +-- 7 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 pkg/morserino_console/testfile.txt diff --git a/pkg/morserino_channels/channels.go b/pkg/morserino_channels/channels.go index cefab8f..195c5aa 100644 --- a/pkg/morserino_channels/channels.go +++ b/pkg/morserino_channels/channels.go @@ -27,17 +27,20 @@ type MorserinoChannels struct { // Channel for the data flow from serial port to display goroutines MessageBuffer chan string - // Channel indicating that all data has been displayed and that the + // Channel indicating that all data has been displayed and that the // application can be closed DisplayCompleted chan bool + Done chan bool + // Channel used to report back errors in goroutines - Error chan error + Error chan error } //Initialize the channels -func (mc MorserinoChannels) Init() { +func (mc *MorserinoChannels) Init() { mc.MessageBuffer = make(chan string, 10) mc.DisplayCompleted = make(chan bool) + mc.Done = make(chan bool) mc.Error = make(chan error) -} \ No newline at end of file +} diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino_com/listen_com.go index 7387e28..80d752a 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino_com/listen_com.go @@ -36,7 +36,7 @@ import ( const exitString string = "\nExiting...\n" // Main listen function with display to the console -func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels morserino_channels.MorserinoChannels) error { +func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels *morserino_channels.MorserinoChannels) error { //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { @@ -73,7 +73,7 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, } // Main receive loop -func Listen(port io.Reader, channels morserino_channels.MorserinoChannels) error { +func Listen(port io.Reader, channels *morserino_channels.MorserinoChannels) error { // //TODO: needs to be moved as a goroutine // consoleDisplay := morserino_console.ConsoleDisplay{} @@ -131,6 +131,7 @@ func Listen(port io.Reader, channels morserino_channels.MorserinoChannels) error break } } + channels.Done <- true return nil } diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino_com/listen_com_test.go index 5033321..06ddcd8 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino_com/listen_com_test.go @@ -83,10 +83,9 @@ func TestListen_HappyCase(t *testing.T) { testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) var testBuffer safebuffer.Buffer - var mc morserino_channels.MorserinoChannels + mc := &morserino_channels.MorserinoChannels{} mc.Init() - // When // Starts listener function so that we can check what has been actually received go MockListener(mc, &testBuffer) @@ -102,7 +101,7 @@ func TestListen_missedEndMarker(t *testing.T) { testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) var testBuffer safebuffer.Buffer - var mc morserino_channels.MorserinoChannels + mc := &morserino_channels.MorserinoChannels{} mc.Init() // When @@ -121,10 +120,9 @@ func TestListen_EOF(t *testing.T) { testMsg := "\nEOF" mock := iotest.ErrReader(nil) var testBuffer safebuffer.Buffer - var mc morserino_channels.MorserinoChannels + mc := &morserino_channels.MorserinoChannels{} mc.Init() - // When go MockListener(mc, &testBuffer) err := Listen(mock, mc) @@ -141,7 +139,7 @@ func TestListen_withSimulator(t *testing.T) { // Given testMsg := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" var testBuffer safebuffer.Buffer - var mc morserino_channels.MorserinoChannels + mc := &morserino_channels.MorserinoChannels{} mc.Init() // When @@ -154,11 +152,11 @@ func TestListen_withSimulator(t *testing.T) { assert.Equal(t, testBuffer.String(), testMsg+exitString) } -func MockListener(mc morserino_channels.MorserinoChannels, workBuffer *safebuffer.Buffer) { +func MockListener(mc *morserino_channels.MorserinoChannels, workBuffer *safebuffer.Buffer) { for { var output string - output = <- mc.MessageBuffer + output = <-mc.MessageBuffer _, err := workBuffer.Write([]byte(output)) if err != nil { log.Fatal(err) diff --git a/pkg/morserino_console/console_display.go b/pkg/morserino_console/console_display.go index c894bbb..257a6ce 100644 --- a/pkg/morserino_console/console_display.go +++ b/pkg/morserino_console/console_display.go @@ -25,16 +25,19 @@ THE SOFTWARE. import ( "fmt" "io" + "log" "strings" "github.com/on4kjm/morserino_display/pkg/morserino_channels" ) -func ConsoleDisplayListener(mc morserino_channels.MorserinoChannels) { - var display ConsoleDisplay +func ConsoleDisplayListener(mc *morserino_channels.MorserinoChannels, outputStream io.Writer) { + display := &ConsoleDisplay{} + display.w = outputStream + for { var output string - output = <- mc.MessageBuffer + output = <-mc.MessageBuffer display.Add(output) if strings.Contains(output, "\nExiting...\n") { @@ -48,7 +51,8 @@ func ConsoleDisplayListener(mc morserino_channels.MorserinoChannels) { type ConsoleDisplay struct { currentLine strings.Builder newLine string - w io.Writer + // output writer + w io.Writer } func (cd *ConsoleDisplay) String() string { @@ -57,13 +61,18 @@ func (cd *ConsoleDisplay) String() string { } func (cd *ConsoleDisplay) Add(buff string) { + // log.Println("ConsoleDisplay output ", cd.w) if strings.Contains(buff, "=") { //FIXME: is the buffer one char long? It is generally followed by a space - fmt.Println("=") + fmt.Fprintln(cd.w, "=") //FIXME: better string accumulation cd.currentLine.WriteString("=\n") } else { - fmt.Printf("%s", buff) + fmt.Printf( "%s", buff) + _, err := fmt.Fprintf(cd.w, "%s", buff) + if(err != nil) { + log.Fatal("Error writing to file: ", err) + } cd.currentLine.WriteString(buff) } } diff --git a/pkg/morserino_console/console_display_test.go b/pkg/morserino_console/console_display_test.go index 4d27665..ac586f9 100644 --- a/pkg/morserino_console/console_display_test.go +++ b/pkg/morserino_console/console_display_test.go @@ -1,28 +1,47 @@ package morserino_console import ( + "bufio" + // "log" + "os" "strings" "testing" "testing/iotest" "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/morserino_com" + "github.com/stretchr/testify/assert" ) func TestConsoleDisplayListener_happyCase(t *testing.T) { // ** Given testMsg := "Test = test e e" - var mc morserino_channels.MorserinoChannels + mc := &morserino_channels.MorserinoChannels{} mc.Init() + f, err := os.Create("testfile.txt") + assert.NoError(t, err) + + defer f.Close() + + w := bufio.NewWriter(f) + + // ** When - go serialListenerMock(testMsg, mc) - go ConsoleDisplayListener(mc) + go serialListenerMock(testMsg, mc) + go ConsoleDisplayListener(mc, w) + + + <-mc.Done // ** Then + // fmt.Println(buff.String()) + assert.True(t,false) + } // A mock to simulaate the serial port listener goroutine -func serialListenerMock(TestString string, mc morserino_channels.MorserinoChannels) { +func serialListenerMock(TestString string, mc *morserino_channels.MorserinoChannels) { morserino_com.Listen(iotest.OneByteReader(strings.NewReader(TestString)), mc) + return } diff --git a/pkg/morserino_console/testfile.txt b/pkg/morserino_console/testfile.txt new file mode 100644 index 0000000..e69de29 diff --git a/pkg/morserino_core/core.go b/pkg/morserino_core/core.go index bce4bd7..d695b13 100644 --- a/pkg/morserino_core/core.go +++ b/pkg/morserino_core/core.go @@ -34,10 +34,9 @@ import ( func Morserino_console(morserinoPortName string) { // initialize the structure containing all the channels we are going to use - var channels morserino_channels.MorserinoChannels + channels := &morserino_channels.MorserinoChannels{} channels.Init() - // Setting up the EnumPorts to the "real life" implementation var realEnumPorts morserino_com.EnumeratePorts From 19979b1f1a98c425783913143e7dcc4bc9dce902 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Sat, 19 Jun 2021 22:50:33 +0200 Subject: [PATCH 07/15] wip --- pkg/morserino_console/console_display.go | 10 +++++----- pkg/morserino_console/console_display_test.go | 11 +++++++---- pkg/morserino_console/testfile.txt | 3 +++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/morserino_console/console_display.go b/pkg/morserino_console/console_display.go index 257a6ce..2bfe301 100644 --- a/pkg/morserino_console/console_display.go +++ b/pkg/morserino_console/console_display.go @@ -23,15 +23,16 @@ THE SOFTWARE. */ import ( + "bufio" "fmt" - "io" + // "io" "log" "strings" "github.com/on4kjm/morserino_display/pkg/morserino_channels" ) -func ConsoleDisplayListener(mc *morserino_channels.MorserinoChannels, outputStream io.Writer) { +func ConsoleDisplayListener(mc *morserino_channels.MorserinoChannels, outputStream *bufio.Writer) { display := &ConsoleDisplay{} display.w = outputStream @@ -52,7 +53,7 @@ type ConsoleDisplay struct { currentLine strings.Builder newLine string // output writer - w io.Writer + w *bufio.Writer } func (cd *ConsoleDisplay) String() string { @@ -68,9 +69,8 @@ func (cd *ConsoleDisplay) Add(buff string) { //FIXME: better string accumulation cd.currentLine.WriteString("=\n") } else { - fmt.Printf( "%s", buff) _, err := fmt.Fprintf(cd.w, "%s", buff) - if(err != nil) { + if err != nil { log.Fatal("Error writing to file: ", err) } cd.currentLine.WriteString(buff) diff --git a/pkg/morserino_console/console_display_test.go b/pkg/morserino_console/console_display_test.go index ac586f9..3074244 100644 --- a/pkg/morserino_console/console_display_test.go +++ b/pkg/morserino_console/console_display_test.go @@ -26,17 +26,20 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { w := bufio.NewWriter(f) - // ** When - go serialListenerMock(testMsg, mc) + go serialListenerMock(testMsg, mc) go ConsoleDisplayListener(mc, w) - <-mc.Done // ** Then // fmt.Println(buff.String()) - assert.True(t,false) + w.Flush() + fi, err := f.Stat() + assert.NoError(t, err) + var zero int64 + zero = 0 + assert.Greater(t, fi.Size(), zero) } diff --git a/pkg/morserino_console/testfile.txt b/pkg/morserino_console/testfile.txt index e69de29..57260d9 100644 --- a/pkg/morserino_console/testfile.txt +++ b/pkg/morserino_console/testfile.txt @@ -0,0 +1,3 @@ +Test = + test e e +Exiting... From 7133f5ecb242e299b9f7ac16dcb0edc51822d074 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Mon, 21 Jun 2021 14:21:44 +0200 Subject: [PATCH 08/15] WIP --- pkg/morserino_console/console_display_test.go | 23 +++++++++++-------- pkg/morserino_console/testfile.txt | 3 --- pkg/morserino_core/core.go | 8 ++++++- 3 files changed, 21 insertions(+), 13 deletions(-) delete mode 100644 pkg/morserino_console/testfile.txt diff --git a/pkg/morserino_console/console_display_test.go b/pkg/morserino_console/console_display_test.go index 3074244..40e2ef0 100644 --- a/pkg/morserino_console/console_display_test.go +++ b/pkg/morserino_console/console_display_test.go @@ -2,7 +2,7 @@ package morserino_console import ( "bufio" - // "log" + "io/ioutil" "os" "strings" "testing" @@ -19,28 +19,33 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { mc := &morserino_channels.MorserinoChannels{} mc.Init() - f, err := os.Create("testfile.txt") + testFileName := "testfile.txt" + f, err := os.Create(testFileName) assert.NoError(t, err) defer f.Close() w := bufio.NewWriter(f) + // f := bufio.NewWriter(os.Stdout) + // ** When go serialListenerMock(testMsg, mc) go ConsoleDisplayListener(mc, w) - <-mc.Done + <-mc.Done //Waiting here for everything to be orderly completed + + w.Flush() //Just to be sure everything is written to disk // ** Then - // fmt.Println(buff.String()) - w.Flush() - fi, err := f.Stat() + b, err := ioutil.ReadFile(testFileName) assert.NoError(t, err) - var zero int64 - zero = 0 - assert.Greater(t, fi.Size(), zero) + expectedOutput := "Test =\n test e e\nExiting...\n" + assert.Equal(t, expectedOutput, string(b)) + + deleteErr := os.Remove(testFileName) + assert.NoError(t, deleteErr) } // A mock to simulaate the serial port listener goroutine diff --git a/pkg/morserino_console/testfile.txt b/pkg/morserino_console/testfile.txt deleted file mode 100644 index 57260d9..0000000 --- a/pkg/morserino_console/testfile.txt +++ /dev/null @@ -1,3 +0,0 @@ -Test = - test e e -Exiting... diff --git a/pkg/morserino_core/core.go b/pkg/morserino_core/core.go index d695b13..1abda1b 100644 --- a/pkg/morserino_core/core.go +++ b/pkg/morserino_core/core.go @@ -23,11 +23,14 @@ THE SOFTWARE. */ import ( + "bufio" "fmt" "log" + "os" "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/morserino_com" + "github.com/on4kjm/morserino_display/pkg/morserino_console" ) // Main entry point for console output @@ -40,7 +43,10 @@ func Morserino_console(morserinoPortName string) { // Setting up the EnumPorts to the "real life" implementation var realEnumPorts morserino_com.EnumeratePorts - morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, channels) + go morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, channels) + go morserino_console.ConsoleDisplayListener(channels, bufio.NewWriter(os.Stdout)) + + <-channels.Done //Waiting here for everything to be orderly completed } //Main entry point for listing ports From 3893674633648abb01cc4e3299a718ec18dca017 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Tue, 22 Jun 2021 09:05:45 +0200 Subject: [PATCH 09/15] WIP --- go.mod | 1 + go.sum | 4 ++ pkg/morserino_com/listen_com.go | 23 ++++++++-- pkg/morserino_com/listen_com_test.go | 42 +++++++++++++++++-- pkg/morserino_console/console_display.go | 14 ++++++- pkg/morserino_console/console_display_test.go | 31 +++++++++++++- 6 files changed, 103 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 7f9ce14..272c22a 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/mitchellh/go-homedir v1.1.0 + github.com/rs/zerolog v1.23.0 github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.8.0 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index eed2a61..e5e1f3b 100644 --- a/go.sum +++ b/go.sum @@ -240,6 +240,7 @@ github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5d github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -257,6 +258,9 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= +github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino_com/listen_com.go index 80d752a..02c3ced 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino_com/listen_com.go @@ -25,14 +25,16 @@ THE SOFTWARE. import ( "fmt" "io" - "log" "strings" "testing/iotest" "github.com/on4kjm/morserino_display/pkg/morserino_channels" + "github.com/rs/zerolog" "go.bug.st/serial" ) +var AppLogger zerolog.Logger + const exitString string = "\nExiting...\n" // Main listen function with display to the console @@ -40,12 +42,14 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { + AppLogger.Debug().Msg("Simulator mode listener") TestMessage := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), channels) } //If portname "auto" was specified, we scan for the Morserino port if strings.ToUpper(morserinoPortName) == "AUTO" { + AppLogger.Debug().Msg("Tying to detect the morsorino port") portName, err := DetectDevice(genericEnumPorts) if err != nil { return err @@ -53,7 +57,7 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, morserinoPortName = portName } - log.Println("Listening to port \"" + morserinoPortName + "\"") + AppLogger.Info().Msg("Listening to port \"" + morserinoPortName + "\"") //Port parameters for a Morserino mode := &serial.Mode{ @@ -63,8 +67,10 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, StopBits: serial.OneStopBit, } + AppLogger.Debug().Msg("Trying to open " + morserinoPortName) p, err := serial.Open(morserinoPortName, mode) if err != nil { + AppLogger.Error().Err(err).Msg("Error opening port") return err } defer p.Close() @@ -90,7 +96,7 @@ func Listen(port io.Reader, channels *morserino_channels.MorserinoChannels) erro // Reads up to 100 bytes n, err := port.Read(buff) if err != nil { - log.Fatal(err) + AppLogger.Error().Err(err).Msg("Error reading on port") } // Check whether the "end of transmission" was sent @@ -112,12 +118,16 @@ func Listen(port io.Reader, channels *morserino_channels.MorserinoChannels) erro } if n == 0 { - fmt.Println("\nEOF") + AppLogger.Debug().Msg("EOF detectd") + AppLogger.Trace().Msg("Sending EOF to the console displayer") channels.MessageBuffer <- "\nEOF" //sending the exit marker to the diplay goroutine + AppLogger.Trace().Msg("Sending exit marker to the console displayer") channels.MessageBuffer <- exitString //waiting for it to complete (blocking read) + AppLogger.Debug().Msg("Waiting for the signal that the display processing was completed") <-channels.DisplayCompleted + AppLogger.Debug().Msg("Display processing completed (received signal)") break } @@ -125,14 +135,19 @@ func Listen(port io.Reader, channels *morserino_channels.MorserinoChannels) erro if closeRequested { //sending the exit marker to the diplay goroutine + AppLogger.Trace().Msg("Sending exit marker to the console displayer as we received the exit sequence") channels.MessageBuffer <- exitString //waiting for it to complete (blocking read) + AppLogger.Debug().Msg("Waiting for the signal that the display processing was completed") <-channels.DisplayCompleted + AppLogger.Debug().Msg("Display processing completed (received signal)") break } } + AppLogger.Debug().Msg("Sending signal that all processing is done") channels.Done <- true + AppLogger.Debug().Msg("Exiting Listen") return nil } diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino_com/listen_com_test.go index 06ddcd8..9a6c26d 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino_com/listen_com_test.go @@ -2,18 +2,38 @@ package morserino_com import ( "fmt" - "log" + "os" "strings" "testing" "testing/iotest" + "time" "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/safebuffer" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.bug.st/serial/enumerator" ) +func init() { + output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} + output.FormatLevel = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) + } + + output.FormatFieldName = func(i interface{}) string { + return fmt.Sprintf("%s:", i) + } + output.FormatFieldValue = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("%s", i)) + } + AppLogger = zerolog.New(output).With().Timestamp().Caller().Logger() + + zerolog.SetGlobalLevel(zerolog.TraceLevel) + +} + // // Testing DetectDevice() // @@ -88,11 +108,18 @@ func TestListen_HappyCase(t *testing.T) { // When // Starts listener function so that we can check what has been actually received + AppLogger.Debug().Msg("Starting the mock listener in the background") go MockListener(mc, &testBuffer) - err := Listen(mock, mc) + AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") + //FIXME: How to receive errors + // err := Listen(mock, mc) + go Listen(mock, mc) + + AppLogger.Debug().Msg("Waiting for the done signal") + <-mc.Done // Then - require.NoError(t, err) + // require.NoError(t, err) assert.Equal(t, testBuffer.String(), testMsg+exitString) } @@ -106,7 +133,9 @@ func TestListen_missedEndMarker(t *testing.T) { // When // Starts listener function so that we can check what has been actually received + AppLogger.Debug().Msg("Starting the mock listener in the background") go MockListener(mc, &testBuffer) + AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") err := Listen(mock, mc) // Then @@ -124,7 +153,9 @@ func TestListen_EOF(t *testing.T) { mc.Init() // When + AppLogger.Debug().Msg("Starting the mock listener in the background") go MockListener(mc, &testBuffer) + AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") err := Listen(mock, mc) // Then @@ -144,7 +175,9 @@ func TestListen_withSimulator(t *testing.T) { // When // Starts listener function so that we can check what has been actually received + AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") go MockListener(mc, &testBuffer) + AppLogger.Debug().Msg("Starting the OpenAndListen() under test (with simulator)") err := OpenAndListen("simul", nil, mc) // Then @@ -159,10 +192,11 @@ func MockListener(mc *morserino_channels.MorserinoChannels, workBuffer *safebuff output = <-mc.MessageBuffer _, err := workBuffer.Write([]byte(output)) if err != nil { - log.Fatal(err) + AppLogger.Error().Err(err).Msg("Error writing to safebuffer") } if strings.Contains(output, "\nExiting...\n") { + AppLogger.Debug().Msg("Signaling that the display processing is complete") mc.DisplayCompleted <- true return } diff --git a/pkg/morserino_console/console_display.go b/pkg/morserino_console/console_display.go index 2bfe301..8102a59 100644 --- a/pkg/morserino_console/console_display.go +++ b/pkg/morserino_console/console_display.go @@ -25,17 +25,24 @@ THE SOFTWARE. import ( "bufio" "fmt" + + // "os" + // "io" - "log" "strings" "github.com/on4kjm/morserino_display/pkg/morserino_channels" + "github.com/rs/zerolog" ) +var AppLogger zerolog.Logger + func ConsoleDisplayListener(mc *morserino_channels.MorserinoChannels, outputStream *bufio.Writer) { display := &ConsoleDisplay{} display.w = outputStream + AppLogger.Debug().Msg("Entering Console Display Listener") + for { var output string output = <-mc.MessageBuffer @@ -43,9 +50,11 @@ func ConsoleDisplayListener(mc *morserino_channels.MorserinoChannels, outputStre if strings.Contains(output, "\nExiting...\n") { mc.DisplayCompleted <- true + AppLogger.Debug().Msg("Exiting Console Display Listener") return } } + } //FIXME: Add comment @@ -71,7 +80,8 @@ func (cd *ConsoleDisplay) Add(buff string) { } else { _, err := fmt.Fprintf(cd.w, "%s", buff) if err != nil { - log.Fatal("Error writing to file: ", err) + // log.Fatal("Error writing to file: ", err) + AppLogger.Error().Err(err).Msg("Error writing to file") } cd.currentLine.WriteString(buff) } diff --git a/pkg/morserino_console/console_display_test.go b/pkg/morserino_console/console_display_test.go index 40e2ef0..07d80b8 100644 --- a/pkg/morserino_console/console_display_test.go +++ b/pkg/morserino_console/console_display_test.go @@ -2,23 +2,47 @@ package morserino_console import ( "bufio" + "fmt" "io/ioutil" "os" "strings" "testing" "testing/iotest" + "time" "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/morserino_com" + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) +func init() { + output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} + output.FormatLevel = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) + } + + output.FormatFieldName = func(i interface{}) string { + return fmt.Sprintf("%s:", i) + } + output.FormatFieldValue = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("%s", i)) + } + + AppLogger = zerolog.New(output).With().Timestamp().Logger() + + zerolog.SetGlobalLevel(zerolog.TraceLevel) + +} + func TestConsoleDisplayListener_happyCase(t *testing.T) { + // ** Given testMsg := "Test = test e e" mc := &morserino_channels.MorserinoChannels{} mc.Init() + //TODO: Create a temporary file in the temporary directory testFileName := "testfile.txt" f, err := os.Create(testFileName) assert.NoError(t, err) @@ -26,14 +50,15 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { defer f.Close() w := bufio.NewWriter(f) - - // f := bufio.NewWriter(os.Stdout) + AppLogger.Debug().Msg("Starting test, output to \"" + testFileName + "\"") // ** When go serialListenerMock(testMsg, mc) go ConsoleDisplayListener(mc, w) + AppLogger.Debug().Msg("Waiting on Done channel") <-mc.Done //Waiting here for everything to be orderly completed + AppLogger.Debug().Msg("All is completed. Go Routines exited") w.Flush() //Just to be sure everything is written to disk @@ -46,6 +71,8 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { deleteErr := os.Remove(testFileName) assert.NoError(t, deleteErr) + + assert.Equal(t, true, false) } // A mock to simulaate the serial port listener goroutine From 9e0ac7f61afb091a2d32944615c082536776926a Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Tue, 22 Jun 2021 12:54:41 +0200 Subject: [PATCH 10/15] reorganize sources --- cmd/console.go | 4 +- cmd/list.go | 4 +- .../channels.go | 2 +- .../list_com.go => morserino/com_list.go} | 2 +- .../com_list_test.go} | 2 +- .../listen_com.go => morserino/com_listen.go} | 10 +-- .../com_listen_test.go} | 86 ++++++++++++------- .../console_display.go | 8 +- .../console_display_test.go | 14 ++- pkg/{morserino_core => morserino}/core.go | 20 ++--- pkg/morserino/core_test.go | 41 +++++++++ .../mock_PortEnumerator.go | 2 +- 12 files changed, 127 insertions(+), 68 deletions(-) rename pkg/{morserino_channels => morserino}/channels.go (98%) rename pkg/{morserino_com/list_com.go => morserino/com_list.go} (99%) rename pkg/{morserino_com/list_com_test.go => morserino/com_list_test.go} (99%) rename pkg/{morserino_com/listen_com.go => morserino/com_listen.go} (94%) rename pkg/{morserino_com/listen_com_test.go => morserino/com_listen_test.go} (70%) rename pkg/{morserino_console => morserino}/console_display.go (90%) rename pkg/{morserino_console => morserino}/console_display_test.go (78%) rename pkg/{morserino_core => morserino}/core.go (74%) create mode 100644 pkg/morserino/core_test.go rename pkg/{morserino_com => morserino}/mock_PortEnumerator.go (98%) diff --git a/cmd/console.go b/cmd/console.go index f65696f..4744853 100644 --- a/cmd/console.go +++ b/cmd/console.go @@ -22,7 +22,7 @@ THE SOFTWARE. package cmd import ( - "github.com/on4kjm/morserino_display/pkg/morserino_core" + "github.com/on4kjm/morserino_display/pkg/morserino" "github.com/spf13/cobra" ) @@ -37,7 +37,7 @@ Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { - morserino_core.Morserino_console(morserinoPortName) + morserino.Morserino_console(morserinoPortName) }, } diff --git a/cmd/list.go b/cmd/list.go index 13c95ae..be0e78b 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -22,7 +22,7 @@ THE SOFTWARE. package cmd import ( - "github.com/on4kjm/morserino_display/pkg/morserino_core" + "github.com/on4kjm/morserino_display/pkg/morserino" "github.com/spf13/cobra" ) @@ -34,7 +34,7 @@ var listCmd = &cobra.Command{ Long: `Displays the ports available on the system. `, Run: func(cmd *cobra.Command, args []string) { - morserino_core.Morserino_list() + morserino.Morserino_list() }, } diff --git a/pkg/morserino_channels/channels.go b/pkg/morserino/channels.go similarity index 98% rename from pkg/morserino_channels/channels.go rename to pkg/morserino/channels.go index 195c5aa..01c90e8 100644 --- a/pkg/morserino_channels/channels.go +++ b/pkg/morserino/channels.go @@ -1,4 +1,4 @@ -package morserino_channels +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM diff --git a/pkg/morserino_com/list_com.go b/pkg/morserino/com_list.go similarity index 99% rename from pkg/morserino_com/list_com.go rename to pkg/morserino/com_list.go index 5dd4198..2437989 100644 --- a/pkg/morserino_com/list_com.go +++ b/pkg/morserino/com_list.go @@ -1,4 +1,4 @@ -package morserino_com +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM diff --git a/pkg/morserino_com/list_com_test.go b/pkg/morserino/com_list_test.go similarity index 99% rename from pkg/morserino_com/list_com_test.go rename to pkg/morserino/com_list_test.go index 12b6ab9..9297d1d 100644 --- a/pkg/morserino_com/list_com_test.go +++ b/pkg/morserino/com_list_test.go @@ -1,4 +1,4 @@ -package morserino_com +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM diff --git a/pkg/morserino_com/listen_com.go b/pkg/morserino/com_listen.go similarity index 94% rename from pkg/morserino_com/listen_com.go rename to pkg/morserino/com_listen.go index 02c3ced..017a358 100644 --- a/pkg/morserino_com/listen_com.go +++ b/pkg/morserino/com_listen.go @@ -1,4 +1,4 @@ -package morserino_com +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM @@ -28,17 +28,15 @@ import ( "strings" "testing/iotest" - "github.com/on4kjm/morserino_display/pkg/morserino_channels" - "github.com/rs/zerolog" + // "github.com/rs/zerolog" "go.bug.st/serial" ) -var AppLogger zerolog.Logger const exitString string = "\nExiting...\n" // Main listen function with display to the console -func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels *morserino_channels.MorserinoChannels) error { +func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels *MorserinoChannels) error { //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { @@ -79,7 +77,7 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, } // Main receive loop -func Listen(port io.Reader, channels *morserino_channels.MorserinoChannels) error { +func Listen(port io.Reader, channels *MorserinoChannels) error { // //TODO: needs to be moved as a goroutine // consoleDisplay := morserino_console.ConsoleDisplay{} diff --git a/pkg/morserino_com/listen_com_test.go b/pkg/morserino/com_listen_test.go similarity index 70% rename from pkg/morserino_com/listen_com_test.go rename to pkg/morserino/com_listen_test.go index 9a6c26d..e8e8dd3 100644 --- a/pkg/morserino_com/listen_com_test.go +++ b/pkg/morserino/com_listen_test.go @@ -1,34 +1,33 @@ -package morserino_com +package morserino import ( "fmt" + // "io/ioutil" "os" "strings" "testing" "testing/iotest" - "time" - "github.com/on4kjm/morserino_display/pkg/morserino_channels" "github.com/on4kjm/morserino_display/pkg/safebuffer" "github.com/rs/zerolog" + // "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.bug.st/serial/enumerator" ) func init() { - output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} - output.FormatLevel = func(i interface{}) string { - return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) - } + // create a temp file + // tempFile, err := ioutil.TempFile(".","testLog") + // if err != nil { + // // Can we log an error before we have our logger? :) + // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") + // } - output.FormatFieldName = func(i interface{}) string { - return fmt.Sprintf("%s:", i) - } - output.FormatFieldValue = func(i interface{}) string { - return strings.ToUpper(fmt.Sprintf("%s", i)) - } - AppLogger = zerolog.New(output).With().Timestamp().Caller().Logger() + zerolog.TimeFieldFormat = "15:04:05.000" + AppLogger= zerolog.New(os.Stdout).With().Timestamp().Logger() + + // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) zerolog.SetGlobalLevel(zerolog.TraceLevel) @@ -99,17 +98,19 @@ func TestDetectDevice_PortEnumerationWentWrong(t *testing.T) { // func TestListen_HappyCase(t *testing.T) { + AppLogger.Info().Msg("==> " + t.Name()) + // Given testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) var testBuffer safebuffer.Buffer - mc := &morserino_channels.MorserinoChannels{} + mc := &MorserinoChannels{} mc.Init() // When // Starts listener function so that we can check what has been actually received AppLogger.Debug().Msg("Starting the mock listener in the background") - go MockListener(mc, &testBuffer) + go MockMorserinoDisplayer(mc, &testBuffer) AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") //FIXME: How to receive errors // err := Listen(mock, mc) @@ -117,75 +118,99 @@ func TestListen_HappyCase(t *testing.T) { AppLogger.Debug().Msg("Waiting for the done signal") <-mc.Done + AppLogger.Debug().Msg("All is completed. Go Routines exited") // Then // require.NoError(t, err) assert.Equal(t, testBuffer.String(), testMsg+exitString) + + AppLogger.Info().Msg("<== " + t.Name()) } func TestListen_missedEndMarker(t *testing.T) { + AppLogger.Info().Msg("==> " + t.Name()) + // Given testMsg := "Test = test e e" mock := iotest.OneByteReader(strings.NewReader(testMsg)) var testBuffer safebuffer.Buffer - mc := &morserino_channels.MorserinoChannels{} + mc := &MorserinoChannels{} mc.Init() // When // Starts listener function so that we can check what has been actually received AppLogger.Debug().Msg("Starting the mock listener in the background") - go MockListener(mc, &testBuffer) + go MockMorserinoDisplayer(mc, &testBuffer) AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") - err := Listen(mock, mc) + go Listen(mock, mc) + + AppLogger.Debug().Msg("Waiting for the done signal") + <-mc.Done + AppLogger.Debug().Msg("All is completed. Go Routines exited") // Then - require.NoError(t, err) assert.Equal(t, testMsg+exitString, testBuffer.String()) + + AppLogger.Info().Msg("<== " + t.Name()) } //EOF error (no error but no data returned) func TestListen_EOF(t *testing.T) { + AppLogger.Info().Msg("==> " + t.Name()) + // Given testMsg := "\nEOF" mock := iotest.ErrReader(nil) var testBuffer safebuffer.Buffer - mc := &morserino_channels.MorserinoChannels{} + mc := &MorserinoChannels{} mc.Init() // When AppLogger.Debug().Msg("Starting the mock listener in the background") - go MockListener(mc, &testBuffer) + go MockMorserinoDisplayer(mc, &testBuffer) AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") - err := Listen(mock, mc) + go Listen(mock, mc) + + AppLogger.Debug().Msg("Waiting for the done signal") + <-mc.Done + AppLogger.Debug().Msg("All is completed. Go Routines exited") // Then - require.NoError(t, err) assert.Equal(t, testMsg+exitString, testBuffer.String()) + + AppLogger.Info().Msg("<== " + t.Name()) } // // Test Listen() // func TestListen_withSimulator(t *testing.T) { + AppLogger.Info().Msg("==> " + t.Name()) + // Given testMsg := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" var testBuffer safebuffer.Buffer - mc := &morserino_channels.MorserinoChannels{} + mc := &MorserinoChannels{} mc.Init() // When // Starts listener function so that we can check what has been actually received AppLogger.Debug().Msg("Starting the Listen under test (with the mock port reader)") - go MockListener(mc, &testBuffer) + go MockMorserinoDisplayer(mc, &testBuffer) AppLogger.Debug().Msg("Starting the OpenAndListen() under test (with simulator)") - err := OpenAndListen("simul", nil, mc) + go OpenAndListen("simul", nil, mc) + + AppLogger.Debug().Msg("Waiting for the done signal") + <-mc.Done + AppLogger.Debug().Msg("All is completed. Go Routines exited") // Then - require.NoError(t, err) assert.Equal(t, testBuffer.String(), testMsg+exitString) + + AppLogger.Info().Msg("<== " + t.Name()) } -func MockListener(mc *morserino_channels.MorserinoChannels, workBuffer *safebuffer.Buffer) { +func MockMorserinoDisplayer(mc *MorserinoChannels, workBuffer *safebuffer.Buffer) { for { var output string @@ -196,10 +221,9 @@ func MockListener(mc *morserino_channels.MorserinoChannels, workBuffer *safebuff } if strings.Contains(output, "\nExiting...\n") { - AppLogger.Debug().Msg("Signaling that the display processing is complete") + AppLogger.Trace().Msg("MockMorserinoDisplayer: Signaling that the display processing is complete") mc.DisplayCompleted <- true return } } - } diff --git a/pkg/morserino_console/console_display.go b/pkg/morserino/console_display.go similarity index 90% rename from pkg/morserino_console/console_display.go rename to pkg/morserino/console_display.go index 8102a59..ad0a7bc 100644 --- a/pkg/morserino_console/console_display.go +++ b/pkg/morserino/console_display.go @@ -1,4 +1,4 @@ -package morserino_console +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM @@ -31,13 +31,10 @@ import ( // "io" "strings" - "github.com/on4kjm/morserino_display/pkg/morserino_channels" - "github.com/rs/zerolog" ) -var AppLogger zerolog.Logger -func ConsoleDisplayListener(mc *morserino_channels.MorserinoChannels, outputStream *bufio.Writer) { +func ConsoleDisplayListener(mc *MorserinoChannels, outputStream *bufio.Writer) { display := &ConsoleDisplay{} display.w = outputStream @@ -83,6 +80,7 @@ func (cd *ConsoleDisplay) Add(buff string) { // log.Fatal("Error writing to file: ", err) AppLogger.Error().Err(err).Msg("Error writing to file") } + cd.w.Flush() cd.currentLine.WriteString(buff) } } diff --git a/pkg/morserino_console/console_display_test.go b/pkg/morserino/console_display_test.go similarity index 78% rename from pkg/morserino_console/console_display_test.go rename to pkg/morserino/console_display_test.go index 07d80b8..0a5cd20 100644 --- a/pkg/morserino_console/console_display_test.go +++ b/pkg/morserino/console_display_test.go @@ -1,4 +1,4 @@ -package morserino_console +package morserino import ( "bufio" @@ -8,16 +8,14 @@ import ( "strings" "testing" "testing/iotest" - "time" + // "time" - "github.com/on4kjm/morserino_display/pkg/morserino_channels" - "github.com/on4kjm/morserino_display/pkg/morserino_com" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) func init() { - output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} + output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05.000"} output.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) } @@ -39,7 +37,7 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { // ** Given testMsg := "Test = test e e" - mc := &morserino_channels.MorserinoChannels{} + mc := &MorserinoChannels{} mc.Init() //TODO: Create a temporary file in the temporary directory @@ -76,7 +74,7 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { } // A mock to simulaate the serial port listener goroutine -func serialListenerMock(TestString string, mc *morserino_channels.MorserinoChannels) { - morserino_com.Listen(iotest.OneByteReader(strings.NewReader(TestString)), mc) +func serialListenerMock(TestString string, mc *MorserinoChannels) { + Listen(iotest.OneByteReader(strings.NewReader(TestString)), mc) return } diff --git a/pkg/morserino_core/core.go b/pkg/morserino/core.go similarity index 74% rename from pkg/morserino_core/core.go rename to pkg/morserino/core.go index 1abda1b..de385f8 100644 --- a/pkg/morserino_core/core.go +++ b/pkg/morserino/core.go @@ -1,4 +1,4 @@ -package morserino_core +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM @@ -28,23 +28,23 @@ import ( "log" "os" - "github.com/on4kjm/morserino_display/pkg/morserino_channels" - "github.com/on4kjm/morserino_display/pkg/morserino_com" - "github.com/on4kjm/morserino_display/pkg/morserino_console" + "github.com/rs/zerolog" ) +var AppLogger zerolog.Logger + // Main entry point for console output func Morserino_console(morserinoPortName string) { // initialize the structure containing all the channels we are going to use - channels := &morserino_channels.MorserinoChannels{} + channels := &MorserinoChannels{} channels.Init() // Setting up the EnumPorts to the "real life" implementation - var realEnumPorts morserino_com.EnumeratePorts + var realEnumPorts EnumeratePorts - go morserino_com.OpenAndListen(morserinoPortName, realEnumPorts, channels) - go morserino_console.ConsoleDisplayListener(channels, bufio.NewWriter(os.Stdout)) + go OpenAndListen(morserinoPortName, realEnumPorts, channels) + go ConsoleDisplayListener(channels, bufio.NewWriter(os.Stdout)) <-channels.Done //Waiting here for everything to be orderly completed } @@ -52,10 +52,10 @@ func Morserino_console(morserinoPortName string) { //Main entry point for listing ports func Morserino_list() { //We are going to use the real function to enumerate ports - var realEnumPorts morserino_com.EnumeratePorts + var realEnumPorts EnumeratePorts //Get the pretty printed list of devices - output, err := morserino_com.List_com(realEnumPorts) + output, err := List_com(realEnumPorts) if err != nil { log.Fatal(err) } diff --git a/pkg/morserino/core_test.go b/pkg/morserino/core_test.go new file mode 100644 index 0000000..d94df1b --- /dev/null +++ b/pkg/morserino/core_test.go @@ -0,0 +1,41 @@ +package morserino + +import ( + "os" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" +) + +func init() { + // create a temp file + // tempFile, err := ioutil.TempFile(".","testLog") + // if err != nil { + // // Can we log an error before we have our logger? :) + // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") + // } + + zerolog.TimeFieldFormat = "15:04:05.000" + AppLogger= zerolog.New(os.Stdout).With().Timestamp().Logger() + + // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) + + zerolog.SetGlobalLevel(zerolog.TraceLevel) + +} + +func TestMorserino_console(t *testing.T) { + AppLogger.Info().Msg("==> " + t.Name()) + + // ** Given + + // ** When + Morserino_console("simulator") + + // ** Then + + AppLogger.Info().Msg("<== " + t.Name()) + // assert.Equal(t,true,false) + assert.Fail(t,"Stop") +} diff --git a/pkg/morserino_com/mock_PortEnumerator.go b/pkg/morserino/mock_PortEnumerator.go similarity index 98% rename from pkg/morserino_com/mock_PortEnumerator.go rename to pkg/morserino/mock_PortEnumerator.go index 016f9ee..ebdb54a 100644 --- a/pkg/morserino_com/mock_PortEnumerator.go +++ b/pkg/morserino/mock_PortEnumerator.go @@ -1,4 +1,4 @@ -package morserino_com +package morserino /* Copyright © 2021 Jean-Marc Meessen, ON4KJM From f7ce3c980a503c85b9c487cd47d844cc3aa85023 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Tue, 22 Jun 2021 15:30:54 +0200 Subject: [PATCH 11/15] Fixed "no morserino found" behavior --- pkg/morserino/channels.go | 2 +- pkg/morserino/com_listen.go | 19 +++++++++++++------ pkg/morserino/com_listen_test.go | 16 ++++++++-------- pkg/morserino/console_display.go | 2 -- pkg/morserino/console_display_test.go | 2 -- pkg/morserino/core_test.go | 18 ++++++++---------- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/pkg/morserino/channels.go b/pkg/morserino/channels.go index 01c90e8..0bb9589 100644 --- a/pkg/morserino/channels.go +++ b/pkg/morserino/channels.go @@ -42,5 +42,5 @@ func (mc *MorserinoChannels) Init() { mc.MessageBuffer = make(chan string, 10) mc.DisplayCompleted = make(chan bool) mc.Done = make(chan bool) - mc.Error = make(chan error) + mc.Error = make(chan error, 10) } diff --git a/pkg/morserino/com_listen.go b/pkg/morserino/com_listen.go index 017a358..927b846 100644 --- a/pkg/morserino/com_listen.go +++ b/pkg/morserino/com_listen.go @@ -32,25 +32,29 @@ import ( "go.bug.st/serial" ) - const exitString string = "\nExiting...\n" // Main listen function with display to the console -func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels *MorserinoChannels) error { +func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, channels *MorserinoChannels) { //If requested, use the simulator instead of a real Morserino if strings.HasPrefix("SIMULATOR", strings.ToUpper(morserinoPortName)) { AppLogger.Debug().Msg("Simulator mode listener") TestMessage := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" - return Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), channels) + err := Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), channels) + channels.Error <- err + return } //If portname "auto" was specified, we scan for the Morserino port if strings.ToUpper(morserinoPortName) == "AUTO" { AppLogger.Debug().Msg("Tying to detect the morsorino port") portName, err := DetectDevice(genericEnumPorts) + channels.Error <- err if err != nil { - return err + channels.MessageBuffer <- exitString + channels.Done <- true + return } morserinoPortName = portName } @@ -69,11 +73,14 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, p, err := serial.Open(morserinoPortName, mode) if err != nil { AppLogger.Error().Err(err).Msg("Error opening port") - return err + channels.Error <- err + return } defer p.Close() - return Listen(p, channels) + err = Listen(p, channels) + channels.Error <- err + return } // Main receive loop diff --git a/pkg/morserino/com_listen_test.go b/pkg/morserino/com_listen_test.go index e8e8dd3..0fa8585 100644 --- a/pkg/morserino/com_listen_test.go +++ b/pkg/morserino/com_listen_test.go @@ -17,17 +17,17 @@ import ( ) func init() { - // create a temp file - // tempFile, err := ioutil.TempFile(".","testLog") - // if err != nil { - // // Can we log an error before we have our logger? :) - // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") - // } + // create a temp file + // tempFile, err := ioutil.TempFile(".","testLog") + // if err != nil { + // // Can we log an error before we have our logger? :) + // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") + // } zerolog.TimeFieldFormat = "15:04:05.000" - AppLogger= zerolog.New(os.Stdout).With().Timestamp().Logger() + AppLogger = zerolog.New(os.Stdout).With().Timestamp().Logger() - // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) + // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) zerolog.SetGlobalLevel(zerolog.TraceLevel) diff --git a/pkg/morserino/console_display.go b/pkg/morserino/console_display.go index ad0a7bc..cedef61 100644 --- a/pkg/morserino/console_display.go +++ b/pkg/morserino/console_display.go @@ -30,10 +30,8 @@ import ( // "io" "strings" - ) - func ConsoleDisplayListener(mc *MorserinoChannels, outputStream *bufio.Writer) { display := &ConsoleDisplay{} display.w = outputStream diff --git a/pkg/morserino/console_display_test.go b/pkg/morserino/console_display_test.go index 0a5cd20..438ddfa 100644 --- a/pkg/morserino/console_display_test.go +++ b/pkg/morserino/console_display_test.go @@ -69,8 +69,6 @@ func TestConsoleDisplayListener_happyCase(t *testing.T) { deleteErr := os.Remove(testFileName) assert.NoError(t, deleteErr) - - assert.Equal(t, true, false) } // A mock to simulaate the serial port listener goroutine diff --git a/pkg/morserino/core_test.go b/pkg/morserino/core_test.go index d94df1b..2c0999e 100644 --- a/pkg/morserino/core_test.go +++ b/pkg/morserino/core_test.go @@ -5,21 +5,20 @@ import ( "testing" "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" ) func init() { - // create a temp file - // tempFile, err := ioutil.TempFile(".","testLog") - // if err != nil { - // // Can we log an error before we have our logger? :) - // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") - // } + // create a temp file + // tempFile, err := ioutil.TempFile(".","testLog") + // if err != nil { + // // Can we log an error before we have our logger? :) + // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") + // } zerolog.TimeFieldFormat = "15:04:05.000" - AppLogger= zerolog.New(os.Stdout).With().Timestamp().Logger() + AppLogger = zerolog.New(os.Stdout).With().Timestamp().Logger() - // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) + // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) zerolog.SetGlobalLevel(zerolog.TraceLevel) @@ -37,5 +36,4 @@ func TestMorserino_console(t *testing.T) { AppLogger.Info().Msg("<== " + t.Name()) // assert.Equal(t,true,false) - assert.Fail(t,"Stop") } From d2145bc7bd078d0ffdf4b930456112c87376c172 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Wed, 23 Jun 2021 20:02:52 +0200 Subject: [PATCH 12/15] wip --- cmd/console.go | 1 + cmd/list.go | 1 + cmd/root.go | 11 ++- pkg/morserino/com_listen.go | 4 +- pkg/morserino/core.go | 4 - ...{console_display.go => display_console.go} | 0 ...isplay_test.go => display_console_test.go} | 22 +++++ pkg/morserino/logger.go | 67 ++++++++++++++ pkg/morserino/logger_test.go | 88 +++++++++++++++++++ 9 files changed, 189 insertions(+), 9 deletions(-) rename pkg/morserino/{console_display.go => display_console.go} (100%) rename pkg/morserino/{console_display_test.go => display_console_test.go} (63%) create mode 100644 pkg/morserino/logger.go create mode 100644 pkg/morserino/logger_test.go diff --git a/cmd/console.go b/cmd/console.go index 4744853..01ce1c5 100644 --- a/cmd/console.go +++ b/cmd/console.go @@ -37,6 +37,7 @@ Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { + morserino.SetupLogger(morserinoDebugLevel, morserinoDebugFilename) morserino.Morserino_console(morserinoPortName) }, } diff --git a/cmd/list.go b/cmd/list.go index be0e78b..7326e7d 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -34,6 +34,7 @@ var listCmd = &cobra.Command{ Long: `Displays the ports available on the system. `, Run: func(cmd *cobra.Command, args []string) { + morserino.SetupLogger(morserinoDebugLevel, morserinoDebugFilename) morserino.Morserino_list() }, } diff --git a/cmd/root.go b/cmd/root.go index c790468..16f5faa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,6 +37,12 @@ var cfgFile string //Global variable with the port name var morserinoPortName string +//Global variable with the debug level +var morserinoDebugLevel string + +//Global variable with the name of the log file (if defined) +var morserinoDebugFilename string + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "morserino_display", @@ -69,12 +75,11 @@ func init() { // will be global for your application. rootCmd.PersistentFlags().StringVar(&morserinoPortName, "port", "auto", "Morserino port (if set to \"auto\", will try to identify the port)") + rootCmd.PersistentFlags().StringVar(&morserinoDebugLevel, "debug", "", "Activtes debug tracing and its level (\"info\", \"debug\", \"trace\")") + rootCmd.PersistentFlags().StringVar(&morserinoDebugFilename, "logfile", "", "Specifices a specific filename to collect the debug info (\"stdout\" is a valid filename") rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.morserino_display.yaml)") - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // initConfig reads in config file and ENV variables if set. diff --git a/pkg/morserino/com_listen.go b/pkg/morserino/com_listen.go index 927b846..b2434cb 100644 --- a/pkg/morserino/com_listen.go +++ b/pkg/morserino/com_listen.go @@ -43,7 +43,7 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, TestMessage := "cq cq de on4kjm on4kjm = tks fer call om = ur rst 599 = hw? \n73 de on4kjm = e e" err := Listen(iotest.OneByteReader(strings.NewReader(TestMessage)), channels) channels.Error <- err - return + return } //If portname "auto" was specified, we scan for the Morserino port @@ -80,7 +80,7 @@ func OpenAndListen(morserinoPortName string, genericEnumPorts comPortEnumerator, err = Listen(p, channels) channels.Error <- err - return + return } // Main receive loop diff --git a/pkg/morserino/core.go b/pkg/morserino/core.go index de385f8..b19a9c3 100644 --- a/pkg/morserino/core.go +++ b/pkg/morserino/core.go @@ -27,12 +27,8 @@ import ( "fmt" "log" "os" - - "github.com/rs/zerolog" ) -var AppLogger zerolog.Logger - // Main entry point for console output func Morserino_console(morserinoPortName string) { diff --git a/pkg/morserino/console_display.go b/pkg/morserino/display_console.go similarity index 100% rename from pkg/morserino/console_display.go rename to pkg/morserino/display_console.go diff --git a/pkg/morserino/console_display_test.go b/pkg/morserino/display_console_test.go similarity index 63% rename from pkg/morserino/console_display_test.go rename to pkg/morserino/display_console_test.go index 438ddfa..1e54549 100644 --- a/pkg/morserino/console_display_test.go +++ b/pkg/morserino/display_console_test.go @@ -1,5 +1,27 @@ package morserino +/* +Copyright © 2021 Jean-Marc Meessen, ON4KJM + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + import ( "bufio" "fmt" diff --git a/pkg/morserino/logger.go b/pkg/morserino/logger.go new file mode 100644 index 0000000..4034ae6 --- /dev/null +++ b/pkg/morserino/logger.go @@ -0,0 +1,67 @@ +package morserino + +import ( + "os" + "strings" + "time" + + "github.com/rs/zerolog" +) + +/* +Copyright © 2021 Jean-Marc Meessen, ON4KJM + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +var AppLogger zerolog.Logger + +// Configures the logging subsystem following the CLI parameter +func SetupLogger(morserinoDebugLevel string, morserinoDebugFilename string) { + //is it set? + //if debugleve is set to trace, add the code line number + +} + +//Creates or opens the logger file +func getLoggerFileHandle(morserinoDebugFilename string) (*os.File, error) { + //if "stdout", direct the logger output to it + if strings.ToLower(morserinoDebugFilename) == "stdout" { + // tempFile, err := ioutil.TempFile(os.TempDir(),"deleteme") + return os.Stdout, nil + } + + //if "", create a filename + if morserinoDebugFilename == "" { + morserinoDebugFilename = createUniqueFilename() + } + + //if a filename is specified create or append to the file + return os.OpenFile(morserinoDebugFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + +} + +//Generates a file name on the format "morserinoTrace_yyyymmddhhmmss.log" +func createUniqueFilename() string { + //get current time + dt := time.Now() + + time := dt.Format("20060102150405") + return "morserinoTrace_" + time + ".log" +} diff --git a/pkg/morserino/logger_test.go b/pkg/morserino/logger_test.go new file mode 100644 index 0000000..ed2ae63 --- /dev/null +++ b/pkg/morserino/logger_test.go @@ -0,0 +1,88 @@ +package morserino + +import ( + "bufio" + "os" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetupLogger(t *testing.T) { + +} + +func Test_getLoggerFileHandle_default(t *testing.T) { + + // ** When + logfile, err := getLoggerFileHandle("") + + // ** Then + require.NoError(t, err) + assert.Regexp(t, regexp.MustCompile("morserinoTrace_20"+getTimetampRegExp()+".log"), logfile.Name()) + + //cleanup + err = os.Remove(logfile.Name()) + require.NoError(t, err) +} + +func Test_getLoggerFileHandle_stdout(t *testing.T) { + + // ** When + logfile, err := getLoggerFileHandle("StdOut") + + // ** Then + require.NoError(t, err) + assert.Equal(t, os.Stdout.Name(), logfile.Name()) +} + +func Test_getLoggerFileHandle_create(t *testing.T) { + // ** Given + testLogName := "test.log" + marker := "Killroy was here" + + // ** When + logfile, err := getLoggerFileHandle(testLogName) + require.NoError(t, err) + defer logfile.Close() + + w := bufio.NewWriter(logfile) + i, err := w.WriteString(marker) + require.NoError(t, err) + + err = w.Flush() + require.NoError(t, err) + + + // ** Then + assert.Equal(t, testLogName, logfile.Name()) + + //length should be zero as we just created it + fi, err := logfile.Stat() + require.NoError(t, err) + assert.Equal(t,int64(i),fi.Size()) + + + //cleanup + err = os.Remove(logfile.Name()) + require.NoError(t, err) +} + +func Test_createUniqueFilename(t *testing.T) { + + // ** When + result := createUniqueFilename() + + // ** Then + assert.Regexp(t, regexp.MustCompile("morserinoTrace_20"+getTimetampRegExp()+".log"), result) +} + +func getTimetampRegExp() string { + year := "[0-9][0-9]" + month := "[0-1][0-9]" + day := "[0-3][0-9]" + time := "[0-5][0-9][0-5][0-9][0-5][0-9]" + return (year + month + day + time) +} From ec39eeda6d9f0290dfa1fe0fa3e196c2e057ffa6 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Wed, 23 Jun 2021 22:41:26 +0200 Subject: [PATCH 13/15] logging control added to the CLI --- .gitignore | 3 +- cmd/console.go | 20 +++++--- cmd/list.go | 10 +++- go.mod | 1 + go.sum | 6 +++ pkg/morserino/com_listen_test.go | 8 --- pkg/morserino/logger.go | 35 +++++++++++-- pkg/morserino/logger_test.go | 86 ++++++++++++++++++++++++++++++-- 8 files changed, 144 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index e7fa65b..022a3f1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ morserino_display dist/* notes/help.txt cover.out -coverage.txt \ No newline at end of file +coverage.txt +*.log \ No newline at end of file diff --git a/cmd/console.go b/cmd/console.go index 01ce1c5..e84267c 100644 --- a/cmd/console.go +++ b/cmd/console.go @@ -22,6 +22,9 @@ THE SOFTWARE. package cmd import ( + "fmt" + "os" + "github.com/on4kjm/morserino_display/pkg/morserino" "github.com/spf13/cobra" ) @@ -29,15 +32,14 @@ import ( // consoleCmd represents the console command var consoleCmd = &cobra.Command{ Use: "console", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "Displays the Morserino output to the console", Run: func(cmd *cobra.Command, args []string) { - morserino.SetupLogger(morserinoDebugLevel, morserinoDebugFilename) + err := morserino.SetupLogger(morserinoDebugLevel, morserinoDebugFilename) + if err != nil { + fmt.Println("\nERROR: " + err.Error() + "\n") + cmd.Help() + os.Exit(1) + } morserino.Morserino_console(morserinoPortName) }, } @@ -45,6 +47,8 @@ to quickly create a Cobra application.`, func init() { rootCmd.AddCommand(consoleCmd) + //FIXME: the port parameter should be moved here + // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command diff --git a/cmd/list.go b/cmd/list.go index 7326e7d..3cca327 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -22,6 +22,9 @@ THE SOFTWARE. package cmd import ( + "fmt" + "os" + "github.com/on4kjm/morserino_display/pkg/morserino" "github.com/spf13/cobra" @@ -34,7 +37,12 @@ var listCmd = &cobra.Command{ Long: `Displays the ports available on the system. `, Run: func(cmd *cobra.Command, args []string) { - morserino.SetupLogger(morserinoDebugLevel, morserinoDebugFilename) + err := morserino.SetupLogger(morserinoDebugLevel, morserinoDebugFilename) + if err != nil { + fmt.Println("\nERROR: " + err.Error() + "\n") + cmd.Help() + os.Exit(1) + } morserino.Morserino_list() }, } diff --git a/go.mod b/go.mod index 272c22a..20f8a19 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.8.0 github.com/stretchr/testify v1.7.0 + github.com/tidwall/gjson v1.8.0 go.bug.st/serial v1.1.3 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 ) diff --git a/go.sum b/go.sum index e5e1f3b..ffc1a86 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,12 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ= +github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/pkg/morserino/com_listen_test.go b/pkg/morserino/com_listen_test.go index 0fa8585..a0a2cf9 100644 --- a/pkg/morserino/com_listen_test.go +++ b/pkg/morserino/com_listen_test.go @@ -17,18 +17,10 @@ import ( ) func init() { - // create a temp file - // tempFile, err := ioutil.TempFile(".","testLog") - // if err != nil { - // // Can we log an error before we have our logger? :) - // log.Error().Err(err).Msg("there was an error creating a temporary file four our log") - // } zerolog.TimeFieldFormat = "15:04:05.000" AppLogger = zerolog.New(os.Stdout).With().Timestamp().Logger() - // fmt.Printf("The log file is allocated at %s\n", tempFile.Name()) - zerolog.SetGlobalLevel(zerolog.TraceLevel) } diff --git a/pkg/morserino/logger.go b/pkg/morserino/logger.go index 4034ae6..9167f03 100644 --- a/pkg/morserino/logger.go +++ b/pkg/morserino/logger.go @@ -1,6 +1,7 @@ package morserino import ( + "fmt" "os" "strings" "time" @@ -33,10 +34,38 @@ THE SOFTWARE. var AppLogger zerolog.Logger // Configures the logging subsystem following the CLI parameter -func SetupLogger(morserinoDebugLevel string, morserinoDebugFilename string) { - //is it set? - //if debugleve is set to trace, add the code line number +func SetupLogger(morserinoDebugLevel string, morserinoDebugFilename string) error { + debugLevel := strings.ToLower(morserinoDebugLevel) + + //If no level was defined, we do nothing + if debugLevel == "" { + return nil + } + + if (debugLevel != "trace") && (debugLevel != "info") && (debugLevel != "debug") { + return fmt.Errorf("\"%s\" is not a supported trace level", morserinoDebugLevel) + } + + logFile, err := getLoggerFileHandle(morserinoDebugFilename) + if err != nil { + return err + } + + zerolog.TimeFieldFormat = "15:04:05.000" + //if debuglevel is set to "trace", add the code line number + if debugLevel == "trace" { + AppLogger = zerolog.New(logFile).With().Timestamp().Caller().Logger() + zerolog.SetGlobalLevel(zerolog.TraceLevel) + } else { + AppLogger = zerolog.New(logFile).With().Timestamp().Logger() + if debugLevel == "debug" { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } + } + return nil } //Creates or opens the logger file diff --git a/pkg/morserino/logger_test.go b/pkg/morserino/logger_test.go index ed2ae63..4536aa0 100644 --- a/pkg/morserino/logger_test.go +++ b/pkg/morserino/logger_test.go @@ -2,15 +2,95 @@ package morserino import ( "bufio" + "io/ioutil" "os" "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" ) -func TestSetupLogger(t *testing.T) { +func TestSetupLogger_noLevel(t *testing.T) { + // ** Given + debugLevel := "" + + // ** When + err := SetupLogger(debugLevel, "") + + // ** Then + assert.NoError(t, err) +} + +func TestSetupLogger_badLevel(t *testing.T) { + // ** Given + debugLevel := "blaahhh" + + // ** When + err := SetupLogger(debugLevel, "") + + // ** Then + assert.EqualError(t, err, "\"blaahhh\" is not a supported trace level") +} + +func TestSetupLogger_trace(t *testing.T) { + // ** Given + debugLevel := "trace" + logFilename := "setupLogger_test.log" + testMessage := "Test message" + + // ** When + err := SetupLogger(debugLevel, logFilename) + + // ** Then + assert.NoError(t, err) + AppLogger.Debug().Msg(testMessage) + + //something should be written in the file + jsonData, err := ioutil.ReadFile(logFilename) + assert.NoError(t, err) + + message := gjson.Get(string(jsonData[:]), "message") + assert.Equal(t, testMessage, message.String()) + + //As we requested the trace level, the caller field should be there + caller := gjson.Get(string(jsonData[:]), "caller") + assert.NotEmpty(t, caller.String()) + + // ** cleanup + err = os.Remove(logFilename) + require.NoError(t, err) + +} + +func TestSetupLogger_debug(t *testing.T) { + // ** Given + debugLevel := "debug" + logFilename := "setupLogger_debugTest.log" + testMessage := "Test message" + + // ** When + err := SetupLogger(debugLevel, logFilename) + + // ** Then + assert.NoError(t, err) + AppLogger.Debug().Msg(testMessage) + + //something should be written in the file + jsonData, err := ioutil.ReadFile(logFilename) + assert.NoError(t, err) + + message := gjson.Get(string(jsonData[:]), "message") + assert.Equal(t, testMessage, message.String()) + + //As we requested the trace level, the caller field should be there + caller := gjson.Get(string(jsonData[:]), "caller") + assert.Empty(t, caller.String()) + + // ** cleanup + err = os.Remove(logFilename) + require.NoError(t, err) } @@ -55,15 +135,13 @@ func Test_getLoggerFileHandle_create(t *testing.T) { err = w.Flush() require.NoError(t, err) - // ** Then assert.Equal(t, testLogName, logfile.Name()) //length should be zero as we just created it fi, err := logfile.Stat() require.NoError(t, err) - assert.Equal(t,int64(i),fi.Size()) - + assert.Equal(t, int64(i), fi.Size()) //cleanup err = os.Remove(logfile.Name()) From 1e982d50332364519163f1c2fe927a605673c443 Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Fri, 25 Jun 2021 09:30:45 +0200 Subject: [PATCH 14/15] Wip --- cmd/console.go | 2 +- cmd/list.go | 2 +- notes/img/morserino - happy case.png | Bin 0 -> 35221 bytes notes/notes.md | 10 +++++++++- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 notes/img/morserino - happy case.png diff --git a/cmd/console.go b/cmd/console.go index e84267c..9c16f02 100644 --- a/cmd/console.go +++ b/cmd/console.go @@ -38,7 +38,7 @@ var consoleCmd = &cobra.Command{ if err != nil { fmt.Println("\nERROR: " + err.Error() + "\n") cmd.Help() - os.Exit(1) + os.Exit(1) } morserino.Morserino_console(morserinoPortName) }, diff --git a/cmd/list.go b/cmd/list.go index 3cca327..2292001 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -41,7 +41,7 @@ var listCmd = &cobra.Command{ if err != nil { fmt.Println("\nERROR: " + err.Error() + "\n") cmd.Help() - os.Exit(1) + os.Exit(1) } morserino.Morserino_list() }, diff --git a/notes/img/morserino - happy case.png b/notes/img/morserino - happy case.png new file mode 100644 index 0000000000000000000000000000000000000000..095c6b38b1a4ee53030233ce541cd5d78334f1fd GIT binary patch literal 35221 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

(ICNY8(x1cTYm5-%Jq$ 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 literal 0 HcmV?d00001 diff --git a/notes/notes.md b/notes/notes.md index c9d7fe7..fbd486f 100644 --- a/notes/notes.md +++ b/notes/notes.md @@ -69,5 +69,13 @@ See also [Create a CLI in golang with Cobra](https://codesource.io/create-a-cli- * [Stopping goroutines](https://riptutorial.com/go/example/6055/stopping-goroutines) * [Never start a goroutine without knowing how it will stop](https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop), Dave Cheney, 2016 +### Channels and error handling +* ["Goroutines Error Handling"](https://www.atatus.com/blog/goroutines-error-handling/), blog, 2015 +* ["Go Concurrency Patterns: Pipelines and cancellation"](https://blog.golang.org/pipelines), blog.golang.org, 2014 +* ["Pipeline Patterns in Go: Pipelines with Error-handling and Cancellation"](https://medium.com/statuscode/pipeline-patterns-in-go-a37bb3a7e61d), medium, 2017 +* ["Idiomatic goroutine termination and error handling"](https://stackoverflow.com/questions/40809504/idiomatic-goroutine-termination-and-error-handling), StackOverflow, 2016 +* ["Return error from the channel"](https://stackoverflow.com/questions/25142016/return-error-from-the-channel), StackOverflow, 2014 +* ["Capture Output and Errors of Goroutine Using Channels"](https://stackoverflow.com/questions/48981528/capture-output-and-errors-of-goroutine-using-channels), StackOverflow, 2018 + ## Miscelaneous -* [Buy Me a Coffe badge](https://gist.github.com/gbraad/216f8162d9b382d14b8a097a37cc2c72#file-readme-md) +* [Buy Me a Coffee badge](https://gist.github.com/gbraad/216f8162d9b382d14b8a097a37cc2c72#file-readme-md) From 88f34af6d02db1af1b5886c15a7419db1bd0112b Mon Sep 17 00:00:00 2001 From: Jean-Marc Meessen Date: Sun, 27 Jun 2021 21:42:54 +0200 Subject: [PATCH 15/15] Add architecture drawing --- notes/img/Morserino - architecture.png | Bin 0 -> 59053 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 notes/img/Morserino - architecture.png diff --git a/notes/img/Morserino - architecture.png b/notes/img/Morserino - architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb5db0eacbec8168245b809246b258f2dbb7630 GIT binary patch literal 59053 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 T-xnm7fghL6?#^YK{Nw%u!;h#t literal 0 HcmV?d00001