├── .github ├── FUNDING.yml ├── dependabot.yaml └── workflows │ └── pullRequests.yaml ├── .gitignore ├── LICENSE ├── README.md ├── basic.go ├── colored.go ├── colored_test.go ├── colors.go ├── concurrent.go ├── go.mod ├── go.sum ├── interface.go ├── prefix.go ├── prefix_test.go ├── resources ├── macss.png └── winss.png ├── symbols.go ├── wlog.go └── wlog_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dixonwille 2 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | 14 | # Maintain dependencies for Go Modules 15 | - package-ecosystem: "gomod" 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" 19 | -------------------------------------------------------------------------------- /.github/workflows/pullRequests.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Requests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | permissions: 12 | contents: read 13 | pull-requests: read 14 | 15 | env: 16 | GO_VERSION: stable 17 | 18 | jobs: 19 | build: 20 | strategy: 21 | matrix: 22 | dir: ["."] 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Set up Go 28 | uses: actions/setup-go@v5 29 | with: 30 | go-version: ${{ env.GO_VERSION }} 31 | 32 | - name: Build 33 | run: go build -v ./... 34 | working-directory: ${{ matrix.dir }} 35 | 36 | - name: Test 37 | run: go test -v ./... 38 | working-directory: ${{ matrix.dir }} 39 | 40 | lint: 41 | strategy: 42 | matrix: 43 | dir: ["."] 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | 48 | - name: Set up Go 49 | uses: actions/setup-go@v5 50 | with: 51 | go-version: ${{ env.GO_VERSION }} 52 | 53 | - name: Lint 54 | uses: golangci/golangci-lint-action@v6 55 | with: 56 | working-directory: ${{ matrix.dir }} 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .imdone/ 2 | .DS_Store 3 | *.out 4 | *.html -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Will Dixon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WLog[![Pull Requests](https://github.com/dixonwille/wlog/actions/workflows/pullRequests.yaml/badge.svg?event=push)](https://github.com/dixonwille/wlog/actions/workflows/pullRequests.yaml) 2 | 3 | Package wlog creates simple to use UI structure. The UI is used to simply print 4 | to the screen. There a wrappers that will wrap each other to create a good 5 | looking UI. You can add color and prefixes as well as make it thread safe. 6 | 7 | ## Documentation 8 | 9 | https://pkg.go.dev/github.com/dixonwille/wlog/v3 10 | 11 | ## Installation 12 | 13 | WLog can be added to your go module file by running: 14 | 15 | ```bash 16 | go get github.com/dixonwille/wlog/v3@latest 17 | ``` 18 | 19 | You can them import the library using an import statement: 20 | 21 | ```go 22 | import "github.com/dixonwille/wlog/v3" 23 | ``` 24 | 25 | ## Idea Behind WLog 26 | 27 | I used Mitchellh's [CLI](https://github.com/mitchellh/cli) structure and 28 | wrapping for the different structures. It was a clean look and feel. Plus it 29 | was pretty simple to use. But I didn't want all the other cli stuff that came 30 | with the package so I created this. 31 | 32 | For color I use DavidDenGCN's 33 | [Go-ColorText](https://github.com/daviddengcn/go-colortext). His color package 34 | allows for color that is available cross-platforms. I made a wrapper with all 35 | possible color combinations with his package. So you only have to import this 36 | package (one less line). 37 | 38 | ## Example Usage 39 | 40 | This example creates a new `wlog.UI` instance, simulates a user providing input and calls UI functions that show output. If you wish to try the example and provide your own user input you can replace the `reader` variable with a reader such as `os.Stdin` which will read from a terminal. 41 | 42 | ```go 43 | var ui wlog.UI 44 | reader := strings.NewReader("User Input\r\n") //Simulate user typing "User Input" then pressing [enter] when reading from os.Stdin 45 | ui = wlog.New(reader, os.Stdout, os.Stdout) 46 | ui = wlog.AddPrefix("?", wlog.Cross, " ", "", "", "~", wlog.Check, "!", ui) 47 | ui = wlog.AddConcurrent(ui) 48 | 49 | ui.Ask("Ask question", "") 50 | ui.Error("Error message") 51 | ui.Info("Info message") 52 | ui.Output("Output message") 53 | ui.Running("Running message") 54 | ui.Success("Success message") 55 | ui.Warn("Warning message") 56 | ``` 57 | 58 | Output: 59 | 60 | ``` 61 | ? Ask question 62 | ✗ Error message 63 | Info message 64 | Output message 65 | ~ Running message 66 | ✓ Success message 67 | ! Warning message 68 | ``` 69 | 70 | On Windows it outputs to this (this includes color): 71 | 72 | ![winss](https://raw.githubusercontent.com/dixonwille/wlog/master/resources/winss.png) 73 | 74 | On Mac it outputs to this (this includes color): 75 | 76 | ![macss](https://raw.githubusercontent.com/dixonwille/wlog/master/resources/macss.png) 77 | -------------------------------------------------------------------------------- /basic.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | const ( 12 | timeFormat = "2006-01-02T15-04-05" 13 | ) 14 | 15 | // BasicUI simply writes/reads to correct input/output 16 | // It is not thread safe. 17 | // Pretty simple to wrap your own functions around 18 | type BasicUI struct { 19 | Reader io.Reader 20 | Writer io.Writer 21 | ErrorWriter io.Writer 22 | } 23 | 24 | // Log prefixes to message before writing to Writer. 25 | func (ui *BasicUI) Log(message string) { 26 | timeString := time.Now().Format(timeFormat) 27 | message = timeString + ": " + message 28 | ui.Output(message) 29 | } 30 | 31 | // Output simply writes to Writer. 32 | func (ui *BasicUI) Output(message string) { 33 | fmt.Fprint(ui.Writer, message) 34 | fmt.Fprint(ui.Writer, "\n") 35 | } 36 | 37 | // Success calls Output to write. 38 | // Useful when you want separate colors or prefixes. 39 | func (ui *BasicUI) Success(message string) { 40 | ui.Output(message) 41 | } 42 | 43 | // Info calls Output to write. 44 | // Useful when you want separate colors or prefixes. 45 | func (ui *BasicUI) Info(message string) { 46 | ui.Output(message) 47 | } 48 | 49 | // Error writes message to ErrorWriter. 50 | func (ui *BasicUI) Error(message string) { 51 | if ui.ErrorWriter != nil { 52 | fmt.Fprint(ui.ErrorWriter, message) 53 | fmt.Fprint(ui.ErrorWriter, "\n") 54 | } else { 55 | fmt.Fprint(ui.Writer, message) 56 | fmt.Fprint(ui.Writer, "\n") 57 | } 58 | } 59 | 60 | // Warn calls Error to write. 61 | // Useful when you want separate colors or prefixes. 62 | func (ui *BasicUI) Warn(message string) { 63 | ui.Error(message) 64 | } 65 | 66 | // Running calls Output to write. 67 | // Useful when you want separate colors or prefixes. 68 | func (ui *BasicUI) Running(message string) { 69 | ui.Output(message) 70 | } 71 | 72 | // Ask will call output with message then wait for Reader to print newline (\n). 73 | // If Reader is os.Stdin then that is when ever a user presses [enter]. 74 | // It will clean the response by removing any carriage returns and new lines that if finds. 75 | // Then it will trim the response using the trim variable. 76 | // Use an empty string to specify you do not want to trim. 77 | // If the message is left blank ("") then it will not prompt user before waiting on a response. 78 | func (ui *BasicUI) Ask(message, trim string) (string, error) { 79 | if message != "" { 80 | ui.Output(message) 81 | } 82 | reader := bufio.NewReader(ui.Reader) 83 | res, err := reader.ReadString('\n') 84 | if err != nil { 85 | return "", err 86 | } 87 | res = strings.Replace(res, "\r", "", -1) //this will only be useful under windows 88 | res = strings.Replace(res, "\n", "", -1) 89 | res = strings.Trim(res, trim) 90 | return res, err 91 | } 92 | -------------------------------------------------------------------------------- /colored.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import "github.com/daviddengcn/go-colortext" 4 | 5 | // ColorUI is a wrapper for UI that adds color. 6 | type ColorUI struct { 7 | LogFGColor Color 8 | OutputFGColor Color 9 | SuccessFGColor Color 10 | InfoFGColor Color 11 | ErrorFGColor Color 12 | WarnFGColor Color 13 | RunningFGColor Color 14 | AskFGColor Color 15 | ResponseFGColor Color 16 | LogBGColor Color 17 | OutputBGColor Color 18 | SuccessBGColor Color 19 | InfoBGColor Color 20 | ErrorBGColor Color 21 | WarnBGColor Color 22 | RunningBGColor Color 23 | AskBGColor Color 24 | ResponseBGColor Color 25 | UI UI 26 | } 27 | 28 | // Log calls UI.Log to write. 29 | // LogFGColor and LogBGColor are used for color. 30 | func (ui *ColorUI) Log(message string) { 31 | ct.ChangeColor(ui.LogFGColor.Code, ui.LogFGColor.Bright, ui.LogBGColor.Code, ui.LogBGColor.Bright) 32 | ui.UI.Log(message) 33 | ct.ResetColor() 34 | } 35 | 36 | // Output calls UI.Output to write. 37 | // OutputFGColor and OutputBGColor are used for color. 38 | func (ui *ColorUI) Output(message string) { 39 | ct.ChangeColor(ui.OutputFGColor.Code, ui.OutputFGColor.Bright, ui.OutputBGColor.Code, ui.OutputBGColor.Bright) 40 | ui.UI.Output(message) 41 | ct.ResetColor() 42 | } 43 | 44 | // Success calls UI.Success to write. 45 | // Useful when you want separate colors or prefixes. 46 | // SuccessFGColor and SuccessBGColor are used for color. 47 | func (ui *ColorUI) Success(message string) { 48 | ct.ChangeColor(ui.SuccessFGColor.Code, ui.SuccessFGColor.Bright, ui.SuccessBGColor.Code, ui.SuccessBGColor.Bright) 49 | ui.UI.Success(message) 50 | ct.ResetColor() 51 | } 52 | 53 | // Info calls UI.Info to write. 54 | // Useful when you want separate colors or prefixes. 55 | // InfoFGColor and InfoBGColor are used for color. 56 | func (ui *ColorUI) Info(message string) { 57 | ct.ChangeColor(ui.InfoFGColor.Code, ui.InfoFGColor.Bright, ui.InfoBGColor.Code, ui.InfoBGColor.Bright) 58 | ui.UI.Info(message) 59 | ct.ResetColor() 60 | } 61 | 62 | // Error calls UI.Error to write. 63 | // ErrorFGColor and ErrorBGColor are used for color. 64 | func (ui *ColorUI) Error(message string) { 65 | ct.ChangeColor(ui.ErrorFGColor.Code, ui.ErrorFGColor.Bright, ui.ErrorBGColor.Code, ui.ErrorBGColor.Bright) 66 | ui.UI.Error(message) 67 | ct.ResetColor() 68 | } 69 | 70 | // Warn calls UI.Warn to write. 71 | // Useful when you want separate colors or prefixes. 72 | // WarnFGColor and WarnBGColor are used for color. 73 | func (ui *ColorUI) Warn(message string) { 74 | ct.ChangeColor(ui.WarnFGColor.Code, ui.WarnFGColor.Bright, ui.WarnBGColor.Code, ui.WarnBGColor.Bright) 75 | ui.UI.Warn(message) 76 | ct.ResetColor() 77 | } 78 | 79 | // Running calls UI.Running to write. 80 | // Useful when you want separate colors or prefixes. 81 | // RunningFGColor and RunningBGColor are used for color. 82 | func (ui *ColorUI) Running(message string) { 83 | ct.ChangeColor(ui.RunningFGColor.Code, ui.RunningFGColor.Bright, ui.RunningBGColor.Code, ui.RunningBGColor.Bright) 84 | ui.UI.Running(message) 85 | ct.ResetColor() 86 | } 87 | 88 | //Ask will call UI.Output with message then wait for UI.Ask to return a response and/or error. 89 | //It will clean the response by removing any carriage returns and new lines that if finds. 90 | //Then it will trim the message using the trim variable. 91 | //Use and empty string to specify you do not want to trim. 92 | //If a message is not used ("") then it will not prompt user before waiting on a response. 93 | //AskFGColor and AskBGColor are used for message color. 94 | //ResponseFGColor and ResponseBGColor are used for response color. 95 | func (ui *ColorUI) Ask(message, trim string) (string, error) { 96 | if message != "" { 97 | ct.ChangeColor(ui.AskFGColor.Code, ui.AskFGColor.Bright, ui.AskBGColor.Code, ui.AskBGColor.Bright) 98 | ui.UI.Output(message) 99 | ct.ResetColor() 100 | } 101 | ct.ChangeColor(ui.ResponseFGColor.Code, ui.ResponseFGColor.Bright, ui.ResponseBGColor.Code, ui.ResponseBGColor.Bright) 102 | res, err := ui.UI.Ask("", trim) 103 | ct.ResetColor() 104 | return res, err 105 | } 106 | -------------------------------------------------------------------------------- /colored_test.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestColoredLog(t *testing.T) { 11 | for _, c := range addColorCases { 12 | writer, errWriter, reader := initTest("\r\n") 13 | assert := assert.New(t) 14 | var ui UI 15 | ui = New(reader, writer, errWriter) 16 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 17 | t := time.Now() 18 | ui.Log("Awesome string") 19 | out, err := writer.ReadString((byte)('\n')) 20 | if err != nil { 21 | assert.Fail(err.Error()) 22 | } 23 | _, err = errWriter.ReadString((byte)('\n')) 24 | assert.Equal("EOF", err.Error()) 25 | expectedString := "Awesome string\n" 26 | expectedString = t.Format(timeFormat) + ": " + expectedString 27 | assert.Equal(expectedString, out) 28 | } 29 | } 30 | 31 | func TestColoredOutput(t *testing.T) { 32 | for _, c := range addColorCases { 33 | writer, errWriter, reader := initTest("\r\n") 34 | assert := assert.New(t) 35 | var ui UI 36 | ui = New(reader, writer, errWriter) 37 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 38 | ui.Output("Awesome string") 39 | out, err := writer.ReadString((byte)('\n')) 40 | if err != nil { 41 | assert.Fail(err.Error()) 42 | } 43 | _, err = errWriter.ReadString((byte)('\n')) 44 | assert.Equal("EOF", err.Error()) 45 | expectedString := "Awesome string\n" 46 | assert.Equal(expectedString, out) 47 | } 48 | } 49 | 50 | func TestColoredSuccess(t *testing.T) { 51 | for _, c := range addColorCases { 52 | writer, errWriter, reader := initTest("\r\n") 53 | assert := assert.New(t) 54 | var ui UI 55 | ui = New(reader, writer, errWriter) 56 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 57 | ui.Success("Awesome string") 58 | out, err := writer.ReadString((byte)('\n')) 59 | if err != nil { 60 | assert.Fail(err.Error()) 61 | } 62 | _, err = errWriter.ReadString((byte)('\n')) 63 | assert.Equal("EOF", err.Error()) 64 | expectedString := "Awesome string\n" 65 | assert.Equal(expectedString, out) 66 | } 67 | } 68 | 69 | func TestColoredInfo(t *testing.T) { 70 | for _, c := range addColorCases { 71 | writer, errWriter, reader := initTest("\r\n") 72 | assert := assert.New(t) 73 | var ui UI 74 | ui = New(reader, writer, errWriter) 75 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 76 | ui.Info("Awesome string") 77 | out, err := writer.ReadString((byte)('\n')) 78 | if err != nil { 79 | assert.Fail(err.Error()) 80 | } 81 | _, err = errWriter.ReadString((byte)('\n')) 82 | assert.Equal("EOF", err.Error()) 83 | expectedString := "Awesome string\n" 84 | assert.Equal(expectedString, out) 85 | } 86 | } 87 | 88 | func TestColoredRunning(t *testing.T) { 89 | for _, c := range addColorCases { 90 | writer, errWriter, reader := initTest("\r\n") 91 | assert := assert.New(t) 92 | var ui UI 93 | ui = New(reader, writer, errWriter) 94 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 95 | ui.Running("Awesome string") 96 | out, err := writer.ReadString((byte)('\n')) 97 | if err != nil { 98 | assert.Fail(err.Error()) 99 | } 100 | _, err = errWriter.ReadString((byte)('\n')) 101 | assert.Equal("EOF", err.Error()) 102 | expectedString := "Awesome string\n" 103 | assert.Equal(expectedString, out) 104 | } 105 | } 106 | 107 | func TestColoredAsk(t *testing.T) { 108 | for _, c := range addColorCases { 109 | writer, errWriter, reader := initTest("abc\r\n") 110 | assert := assert.New(t) 111 | var ui UI 112 | ui = New(reader, writer, errWriter) 113 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 114 | res, err := ui.Ask("Awesome string", "") 115 | if err != nil { 116 | assert.Fail(err.Error()) 117 | } 118 | out, err := writer.ReadString((byte)('\n')) 119 | if err != nil { 120 | assert.Fail(err.Error()) 121 | } 122 | _, err = errWriter.ReadString((byte)('\n')) 123 | assert.Equal("EOF", err.Error()) 124 | expectedString := "Awesome string\n" 125 | assert.Equal(expectedString, out) 126 | assert.Equal("abc", res) 127 | } 128 | } 129 | 130 | func TestColoredError(t *testing.T) { 131 | for _, c := range addColorCases { 132 | writer, errWriter, reader := initTest("\r\n") 133 | assert := assert.New(t) 134 | var ui UI 135 | ui = New(reader, writer, errWriter) 136 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 137 | ui.Error("Awesome string") 138 | out, err := errWriter.ReadString((byte)('\n')) 139 | if err != nil { 140 | assert.Fail(err.Error()) 141 | } 142 | _, err = writer.ReadString((byte)('\n')) 143 | assert.Equal("EOF", err.Error()) 144 | expectedString := "Awesome string\n" 145 | assert.Equal(expectedString, out) 146 | } 147 | } 148 | 149 | func TestColoredWarn(t *testing.T) { 150 | for _, c := range addColorCases { 151 | writer, errWriter, reader := initTest("\r\n") 152 | assert := assert.New(t) 153 | var ui UI 154 | ui = New(reader, writer, errWriter) 155 | ui = AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, ui) 156 | ui.Warn("Awesome string") 157 | out, err := errWriter.ReadString((byte)('\n')) 158 | if err != nil { 159 | assert.Fail(err.Error()) 160 | } 161 | _, err = writer.ReadString((byte)('\n')) 162 | assert.Equal("EOF", err.Error()) 163 | expectedString := "Awesome string\n" 164 | assert.Equal(expectedString, out) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /colors.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import "github.com/daviddengcn/go-colortext" 4 | 5 | // Color is a wrapper for go-colortext. 6 | // Simplifies the use of this package by assigning predefined colors that can be used. 7 | type Color struct { 8 | Code ct.Color 9 | Bright bool 10 | } 11 | 12 | var ( 13 | //BrightRed creates a bright red color 14 | BrightRed = Color{ct.Red, true} 15 | 16 | //BrightBlue creates a bright blue color 17 | BrightBlue = Color{ct.Blue, true} 18 | 19 | //BrightYellow creates a bright yellow color 20 | BrightYellow = Color{ct.Yellow, true} 21 | 22 | //Red creates a red color 23 | Red = Color{ct.Red, false} 24 | 25 | //Blue creaets a blue color 26 | Blue = Color{ct.Blue, false} 27 | 28 | //Yellow creates a yellow color 29 | Yellow = Color{ct.Yellow, false} 30 | 31 | //BrightGreen creates a bright green color 32 | BrightGreen = Color{ct.Green, true} 33 | 34 | //BrightCyan creates a bright cyan color 35 | BrightCyan = Color{ct.Cyan, true} 36 | 37 | //BrightMagenta creates a bright magenta color 38 | BrightMagenta = Color{ct.Magenta, true} 39 | 40 | //Green creates a green color 41 | Green = Color{ct.Green, false} 42 | 43 | //Cyan creates a cyan color 44 | Cyan = Color{ct.Cyan, false} 45 | 46 | //Magenta creates a magenta color 47 | Magenta = Color{ct.Magenta, false} 48 | 49 | //White creates a white color 50 | White = Color{ct.White, false} 51 | 52 | //BrightWhite creates a bright white color 53 | BrightWhite = Color{ct.White, true} 54 | 55 | //Black creates a black color 56 | Black = Color{ct.Black, false} 57 | 58 | //BrightBlack creates a bright black color 59 | BrightBlack = Color{ct.Black, true} 60 | 61 | //None does not change the color 62 | None = Color{ct.None, false} 63 | ) 64 | -------------------------------------------------------------------------------- /concurrent.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import "sync" 4 | 5 | // ConcurrentUI is a wrapper for UI that makes the UI thread safe. 6 | type ConcurrentUI struct { 7 | UI UI 8 | l sync.Mutex 9 | } 10 | 11 | // Log calls UI.Log to write. 12 | // This is a thread safe function. 13 | func (ui *ConcurrentUI) Log(message string) { 14 | ui.l.Lock() 15 | defer ui.l.Unlock() 16 | ui.UI.Log(message) 17 | } 18 | 19 | // Output calls UI.Output to write. 20 | // This is a thread safe function. 21 | func (ui *ConcurrentUI) Output(message string) { 22 | ui.l.Lock() 23 | defer ui.l.Unlock() 24 | ui.UI.Output(message) 25 | } 26 | 27 | // Success calls UI.Success to write. 28 | // Useful when you want separate colors or prefixes. 29 | // This is a thread safe function. 30 | func (ui *ConcurrentUI) Success(message string) { 31 | ui.l.Lock() 32 | defer ui.l.Unlock() 33 | ui.UI.Success(message) 34 | } 35 | 36 | // Info calls UI.Info to write. 37 | // Useful when you want separate colors or prefixes. 38 | // This is a thread safe function. 39 | func (ui *ConcurrentUI) Info(message string) { 40 | ui.l.Lock() 41 | defer ui.l.Unlock() 42 | ui.UI.Info(message) 43 | } 44 | 45 | // Error calls UI.Error to write. 46 | // This is a thread safe function. 47 | func (ui *ConcurrentUI) Error(message string) { 48 | ui.l.Lock() 49 | defer ui.l.Unlock() 50 | ui.UI.Error(message) 51 | } 52 | 53 | // Warn calls UI.Warn to write. 54 | // Useful when you want separate colors or prefixes. 55 | // This is a thread safe function. 56 | func (ui *ConcurrentUI) Warn(message string) { 57 | ui.l.Lock() 58 | defer ui.l.Unlock() 59 | ui.UI.Warn(message) 60 | } 61 | 62 | // Running calls UI.Running to write. 63 | // Useful when you want separate colors or prefixes. 64 | // This is a thread safe function. 65 | func (ui *ConcurrentUI) Running(message string) { 66 | ui.l.Lock() 67 | defer ui.l.Unlock() 68 | ui.UI.Running(message) 69 | } 70 | 71 | // Ask will call UI.Ask with message then wait for UI.Ask to return a response and/or error. 72 | // It will clean the response by removing any carriage returns and new lines that if finds. 73 | //Then it will trim the message using the trim variable. 74 | //Use and empty string to specify you do not want to trim. 75 | // If a message is not used ("") then it will not prompt user before waiting on a response. 76 | // This is a thread safe function. 77 | func (ui *ConcurrentUI) Ask(message, trim string) (string, error) { 78 | ui.l.Lock() 79 | defer ui.l.Unlock() 80 | res, err := ui.UI.Ask(message, trim) 81 | return res, err 82 | } 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dixonwille/wlog/v3 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/daviddengcn/go-colortext v1.0.0 7 | github.com/stretchr/testify v1.9.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE= 5 | github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= 6 | github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= 7 | github.com/golangplus/bytes v1.0.0 h1:YQKBijBVMsBxIiXT4IEhlKR2zHohjEqPole4umyDX+c= 8 | github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A= 9 | github.com/golangplus/fmt v1.0.0 h1:FnUKtw86lXIPfBMc3FimNF3+ABcV+aH5F17OOitTN+E= 10 | github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE= 11 | github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ= 12 | github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 17 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 18 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 19 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 21 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 22 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 23 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 28 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | // UI simply writes to an io.Writer with a new line appended to each call. 4 | // It also has the ability to ask a question and return a response. 5 | type UI interface { 6 | // Log writes a timestamped message to the writer 7 | Log(message string) 8 | // Output writes a message to the writer 9 | Output(message string) 10 | // Success writes a message indicating an success message 11 | Success(message string) 12 | // Info writes a message indicating an informational message 13 | Info(message string) 14 | // Error writes a message indicating an error 15 | Error(message string) 16 | // Warn writes a message indicating a warning 17 | Warn(message string) 18 | // Running writes a message indicating a process is running 19 | Running(message string) 20 | // Ask writes a message to the writer and reads the user's input 21 | // Message is written to the writer and the response is trimmed by the trim value 22 | Ask(message string, trim string) (response string, error error) 23 | } 24 | -------------------------------------------------------------------------------- /prefix.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | // PrefixUI is a wrapper for UI that prefixes all strings. 4 | // It does add a space betweem the prefix and message. 5 | // If no prefix is specified ("") then it does not prefix the space. 6 | type PrefixUI struct { 7 | LogPrefix string 8 | OutputPrefix string 9 | SuccessPrefix string 10 | InfoPrefix string 11 | ErrorPrefix string 12 | WarnPrefix string 13 | RunningPrefix string 14 | AskPrefix string 15 | UI UI 16 | } 17 | 18 | // Log calls UI.Log to write. 19 | // LogPrefix is used to prefix the message. 20 | func (ui *PrefixUI) Log(message string) { 21 | if ui.LogPrefix == " " { //Lets keep the space if they want one 22 | message = ui.LogPrefix + message 23 | } else if ui.LogPrefix != "" { 24 | message = ui.LogPrefix + " " + message 25 | } 26 | ui.UI.Log(message) 27 | } 28 | 29 | // Output calls UI.Output to write. 30 | // OutputPrefix is used to prefix the message. 31 | func (ui *PrefixUI) Output(message string) { 32 | if ui.OutputPrefix == " " { //Lets keep the space if they want one 33 | message = ui.OutputPrefix + message 34 | } else if ui.OutputPrefix != "" { 35 | message = ui.OutputPrefix + " " + message 36 | } 37 | ui.UI.Output(message) 38 | } 39 | 40 | // Success calls UI.Success to write. 41 | // Useful when you want separate colors or prefixes. 42 | // SuccessPrefix is used to prefix the message. 43 | func (ui *PrefixUI) Success(message string) { 44 | if ui.SuccessPrefix == " " { //Lets keep the space if they want one 45 | message = ui.SuccessPrefix + message 46 | } else if ui.SuccessPrefix != "" { 47 | message = ui.SuccessPrefix + " " + message 48 | } 49 | ui.UI.Success(message) 50 | } 51 | 52 | // Info calls UI.Info to write. 53 | // Useful when you want separate colors or prefixes. 54 | // InfoPrefix is used to prefix the message. 55 | func (ui *PrefixUI) Info(message string) { 56 | if ui.InfoPrefix == " " { //Lets keep the space if they want one 57 | message = ui.InfoPrefix + message 58 | } else if ui.InfoPrefix != "" { 59 | message = ui.InfoPrefix + " " + message 60 | } 61 | ui.UI.Info(message) 62 | } 63 | 64 | // Error call UI.Error to write. 65 | // ErrorPrefix is used to prefix the message. 66 | func (ui *PrefixUI) Error(message string) { 67 | if ui.ErrorPrefix == " " { //Lets keep the space if they want one 68 | message = ui.ErrorPrefix + message 69 | } else if ui.ErrorPrefix != "" { 70 | message = ui.ErrorPrefix + " " + message 71 | } 72 | ui.UI.Error(message) 73 | } 74 | 75 | // Warn calls UI.Warn to write. 76 | // Useful when you want separate colors or prefixes. 77 | // WarnPrefix is used to prefix message. 78 | func (ui *PrefixUI) Warn(message string) { 79 | if ui.WarnPrefix == " " { //Lets keep the space if they want one 80 | message = ui.WarnPrefix + message 81 | } else if ui.WarnPrefix != "" { 82 | message = ui.WarnPrefix + " " + message 83 | } 84 | ui.UI.Warn(message) 85 | } 86 | 87 | // Running calls Output to write. 88 | // Useful when you want separate colors or prefixes. 89 | // RunningPrefix is used to prefix message. 90 | func (ui *PrefixUI) Running(message string) { 91 | if ui.RunningPrefix == " " { //Lets keep the space if they want one 92 | message = ui.RunningPrefix + message 93 | } else if ui.RunningPrefix != "" { 94 | message = ui.RunningPrefix + " " + message 95 | } 96 | ui.UI.Running(message) 97 | } 98 | 99 | //Ask will call UI.Ask with message then wait for UI.Ask to return a response and/or error. 100 | //It will clean the response by removing any carriage returns and new lines that if finds. 101 | //Then it will trim the message using the trim variable. 102 | //Use and empty string to specify you do not want to trim. 103 | //If a message is not used ("") then it will not prompt user before waiting on a response. 104 | //AskPrefix is used to prefix message. 105 | func (ui *PrefixUI) Ask(message, trim string) (string, error) { 106 | if ui.AskPrefix == " " { //Lets keep the space if they want one 107 | message = ui.AskPrefix + message 108 | } else if ui.AskPrefix != "" { 109 | message = ui.AskPrefix + " " + message 110 | } 111 | res, err := ui.UI.Ask(message, trim) 112 | return res, err 113 | } 114 | -------------------------------------------------------------------------------- /prefix_test.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPrefixConcurrentLog(t *testing.T) { 11 | for _, c := range addPrefixCases { 12 | assert := assert.New(t) 13 | var ui UI 14 | writer, errWriter, reader := initTest("\r\n") 15 | ui = New(reader, writer, errWriter) 16 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 17 | ui = AddConcurrent(ui) 18 | t := time.Now() 19 | ui.Log("Awesome string") 20 | out, err := writer.ReadString((byte)('\n')) 21 | if err != nil { 22 | assert.Fail(err.Error()) 23 | } 24 | _, err = errWriter.ReadString((byte)('\n')) 25 | assert.Equal("EOF", err.Error()) 26 | expectedString := "Awesome string\n" 27 | if c.log == " " { 28 | expectedString = t.Format(timeFormat) + ": " + c.log + expectedString 29 | } else if c.log == "" { 30 | expectedString = t.Format(timeFormat) + ": " + expectedString 31 | } else { 32 | expectedString = t.Format(timeFormat) + ": " + c.log + " " + expectedString 33 | } 34 | assert.Equal(expectedString, out) 35 | } 36 | } 37 | 38 | func TestPrefixOutput(t *testing.T) { 39 | for _, c := range addPrefixCases { 40 | assert := assert.New(t) 41 | var ui UI 42 | writer, errWriter, reader := initTest("\r\n") 43 | ui = New(reader, writer, errWriter) 44 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 45 | ui.Output("Awesome string") 46 | out, err := writer.ReadString((byte)('\n')) 47 | if err != nil { 48 | assert.Fail(err.Error()) 49 | } 50 | _, err = errWriter.ReadString((byte)('\n')) 51 | assert.Equal("EOF", err.Error()) 52 | expectedString := "Awesome string\n" 53 | if c.out == " " { 54 | expectedString = c.out + expectedString 55 | } else if c.out != "" { 56 | expectedString = c.out + " " + expectedString 57 | } 58 | assert.Equal(expectedString, out) 59 | } 60 | } 61 | 62 | func TestPrefixSuccess(t *testing.T) { 63 | for _, c := range addPrefixCases { 64 | assert := assert.New(t) 65 | var ui UI 66 | writer, errWriter, reader := initTest("\r\n") 67 | ui = New(reader, writer, errWriter) 68 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 69 | ui.Success("Awesome string") 70 | out, err := writer.ReadString((byte)('\n')) 71 | if err != nil { 72 | assert.Fail(err.Error()) 73 | } 74 | _, err = errWriter.ReadString((byte)('\n')) 75 | assert.Equal("EOF", err.Error()) 76 | expectedString := "Awesome string\n" 77 | if c.suc == " " { 78 | expectedString = c.suc + expectedString 79 | } else if c.suc != "" { 80 | expectedString = c.suc + " " + expectedString 81 | } 82 | assert.Equal(expectedString, out) 83 | } 84 | } 85 | 86 | func TestPrefixInfo(t *testing.T) { 87 | for _, c := range addPrefixCases { 88 | assert := assert.New(t) 89 | var ui UI 90 | writer, errWriter, reader := initTest("\r\n") 91 | ui = New(reader, writer, errWriter) 92 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 93 | ui.Info("Awesome string") 94 | out, err := writer.ReadString((byte)('\n')) 95 | if err != nil { 96 | assert.Fail(err.Error()) 97 | } 98 | _, err = errWriter.ReadString((byte)('\n')) 99 | assert.Equal("EOF", err.Error()) 100 | expectedString := "Awesome string\n" 101 | if c.inf == " " { 102 | expectedString = c.inf + expectedString 103 | } else if c.inf != "" { 104 | expectedString = c.inf + " " + expectedString 105 | } 106 | assert.Equal(expectedString, out) 107 | } 108 | } 109 | 110 | func TestPrefixRunning(t *testing.T) { 111 | for _, c := range addPrefixCases { 112 | assert := assert.New(t) 113 | var ui UI 114 | writer, errWriter, reader := initTest("\r\n") 115 | ui = New(reader, writer, errWriter) 116 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 117 | ui.Running("Awesome string") 118 | out, err := writer.ReadString((byte)('\n')) 119 | if err != nil { 120 | assert.Fail(err.Error()) 121 | } 122 | _, err = errWriter.ReadString((byte)('\n')) 123 | assert.Equal("EOF", err.Error()) 124 | expectedString := "Awesome string\n" 125 | if c.run == " " { 126 | expectedString = c.run + expectedString 127 | } else if c.run != "" { 128 | expectedString = c.run + " " + expectedString 129 | } 130 | assert.Equal(expectedString, out) 131 | } 132 | } 133 | 134 | func TestPrefixError(t *testing.T) { 135 | for _, c := range addPrefixCases { 136 | assert := assert.New(t) 137 | var ui UI 138 | writer, errWriter, reader := initTest("\r\n") 139 | ui = New(reader, writer, errWriter) 140 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 141 | ui.Error("Awesome string") 142 | out, err := errWriter.ReadString((byte)('\n')) 143 | if err != nil { 144 | assert.Fail(err.Error()) 145 | } 146 | _, err = writer.ReadString((byte)('\n')) 147 | assert.Equal("EOF", err.Error()) 148 | expectedString := "Awesome string\n" 149 | if c.err == " " { 150 | expectedString = c.err + expectedString 151 | } else if c.err != "" { 152 | expectedString = c.err + " " + expectedString 153 | } 154 | assert.Equal(expectedString, out) 155 | } 156 | } 157 | 158 | func TestPrefixWarn(t *testing.T) { 159 | for _, c := range addPrefixCases { 160 | assert := assert.New(t) 161 | var ui UI 162 | writer, errWriter, reader := initTest("\r\n") 163 | ui = New(reader, writer, errWriter) 164 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 165 | ui.Warn("Awesome string") 166 | out, err := errWriter.ReadString((byte)('\n')) 167 | if err != nil { 168 | assert.Fail(err.Error()) 169 | } 170 | _, err = writer.ReadString((byte)('\n')) 171 | assert.Equal("EOF", err.Error()) 172 | expectedString := "Awesome string\n" 173 | if c.war == " " { 174 | expectedString = c.war + expectedString 175 | } else if c.war != "" { 176 | expectedString = c.war + " " + expectedString 177 | } 178 | assert.Equal(expectedString, out) 179 | } 180 | } 181 | 182 | func TestPrefixWarnNoErrorWriter(t *testing.T) { 183 | for _, c := range addPrefixCases { 184 | assert := assert.New(t) 185 | var ui UI 186 | writer, errWriter, reader := initTest("\r\n") 187 | ui = New(reader, writer, nil) 188 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 189 | ui.Warn("Awesome string") 190 | out, err := writer.ReadString((byte)('\n')) 191 | if err != nil { 192 | assert.Fail(err.Error()) 193 | } 194 | _, err = errWriter.ReadString((byte)('\n')) 195 | assert.Equal("EOF", err.Error()) 196 | expectedString := "Awesome string\n" 197 | if c.war == " " { 198 | expectedString = c.war + expectedString 199 | } else if c.war != "" { 200 | expectedString = c.war + " " + expectedString 201 | } 202 | assert.Equal(expectedString, out) 203 | } 204 | } 205 | 206 | func TestPrefixAsk(t *testing.T) { 207 | for _, c := range addPrefixCases { 208 | assert := assert.New(t) 209 | var ui UI 210 | writer, errWriter, reader := initTest("345\r\n") 211 | ui = New(reader, writer, errWriter) 212 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 213 | res, err := ui.Ask("Awesome string", "") 214 | if err != nil { 215 | assert.Fail(err.Error()) 216 | } 217 | out, err := writer.ReadString((byte)('\n')) 218 | if err != nil { 219 | assert.Fail(err.Error()) 220 | } 221 | _, err = errWriter.ReadString((byte)('\n')) 222 | assert.Equal("EOF", err.Error()) 223 | expectedString := "Awesome string\n" 224 | if c.ask == " " { 225 | expectedString = c.ask + expectedString 226 | } else if c.ask != "" { 227 | expectedString = c.ask + " " + expectedString 228 | } 229 | assert.Equal(expectedString, out) 230 | assert.Equal("345", res) 231 | } 232 | } 233 | 234 | func TestPrefixAskError(t *testing.T) { 235 | for _, c := range addPrefixCases { 236 | assert := assert.New(t) 237 | var ui UI 238 | writer, errWriter, reader := initTest("") 239 | ui = New(reader, writer, errWriter) 240 | ui = AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, ui) 241 | res, err := ui.Ask("Awesome string", "") 242 | if err != nil { 243 | assert.Equal("EOF", err.Error()) 244 | } 245 | out, err := writer.ReadString((byte)('\n')) 246 | if err != nil { 247 | assert.Fail(err.Error()) 248 | } 249 | _, err = errWriter.ReadString((byte)('\n')) 250 | assert.Equal("EOF", err.Error()) 251 | expectedString := "Awesome string\n" 252 | if c.ask == " " { 253 | expectedString = c.ask + expectedString 254 | } else if c.ask != "" { 255 | expectedString = c.ask + " " + expectedString 256 | } 257 | assert.Equal(expectedString, out) 258 | assert.Equal("", res) 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /resources/macss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dixonwille/wlog/a39fa152ff7a63ecaa19d9ae816560efb70921b6/resources/macss.png -------------------------------------------------------------------------------- /resources/winss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dixonwille/wlog/a39fa152ff7a63ecaa19d9ae816560efb70921b6/resources/winss.png -------------------------------------------------------------------------------- /symbols.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | const ( 4 | //These symbols should not be used on windows. 5 | //Use runtime.GOOS to check the OS before using. 6 | 7 | //Check displays a checkmark 8 | Check = "\u2713" 9 | 10 | //Cross displays an x 11 | Cross = "\u2717" 12 | ) 13 | -------------------------------------------------------------------------------- /wlog.go: -------------------------------------------------------------------------------- 1 | //Package wlog creates simple to use UI structure. 2 | //The UI is used to simply print to the screen. 3 | //There a wrappers that will wrap each other to create a good looking UI. 4 | //You can add color and prefixes as well as make it thread safe. 5 | package wlog 6 | 7 | //TODO:10 Add a simple way to split writer between terminal and file 8 | //TODO:0 Add a TableUI 9 | import "io" 10 | 11 | //New creates a BasicUI. 12 | //This should be the first function you call. 13 | //This is not thread safe and should only be used in serial applications. 14 | func New(reader io.Reader, writer, errorWriter io.Writer) *BasicUI { 15 | return &BasicUI{ 16 | Reader: reader, 17 | Writer: writer, 18 | ErrorWriter: errorWriter, 19 | } 20 | } 21 | 22 | // AddConcurrent will wrap a thread safe UI on top of ui. 23 | // Safe to use inside of go routines. 24 | func AddConcurrent(ui UI) *ConcurrentUI { 25 | return &ConcurrentUI{UI: ui} 26 | } 27 | 28 | //AddColor will wrap a colorful UI on top of ui. 29 | //Use wlog's color variables for the color. 30 | //All background colors are not changed by this function but you are able to change them manually. 31 | //Just create this structure manually and change any of the background colors you want. 32 | //Arguments are in alphabetical order. 33 | func AddColor(askColor, errorColor, infoColor, logColor, outputColor, responseColor, runningColor, successColor, warnColor Color, ui UI) *ColorUI { 34 | return &ColorUI{ 35 | LogFGColor: logColor, 36 | LogBGColor: None, 37 | OutputFGColor: outputColor, 38 | OutputBGColor: None, 39 | SuccessFGColor: successColor, 40 | SuccessBGColor: None, 41 | InfoFGColor: infoColor, 42 | InfoBGColor: None, 43 | ErrorFGColor: errorColor, 44 | ErrorBGColor: None, 45 | WarnFGColor: warnColor, 46 | WarnBGColor: None, 47 | RunningFGColor: runningColor, 48 | RunningBGColor: None, 49 | AskFGColor: askColor, 50 | AskBGColor: None, 51 | ResponseFGColor: responseColor, 52 | ResponseBGColor: None, 53 | UI: ui, 54 | } 55 | } 56 | 57 | //AddPrefix will wrap a UI that will prefix the message on top of ui. 58 | //If a prefix is set to nothing ("") then there will be no prefix for that message type. 59 | //Arguments are in alphabetical order. 60 | func AddPrefix(askPre, errorPre, infoPre, logPre, outputPre, runningPre, successPre, warnPre string, ui UI) *PrefixUI { 61 | return &PrefixUI{ 62 | LogPrefix: logPre, 63 | OutputPrefix: outputPre, 64 | SuccessPrefix: successPre, 65 | InfoPrefix: infoPre, 66 | ErrorPrefix: errorPre, 67 | WarnPrefix: warnPre, 68 | RunningPrefix: runningPre, 69 | AskPrefix: askPre, 70 | UI: ui, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /wlog_test.go: -------------------------------------------------------------------------------- 1 | package wlog 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var newCases = []struct { 15 | in io.Reader 16 | out io.Writer 17 | err io.Writer 18 | }{ 19 | {strings.NewReader("Hello Worlds\r\n"), os.Stdout, nil}, 20 | {nil, nil, nil}, 21 | {os.Stdin, nil, os.Stderr}, 22 | } 23 | 24 | var addColorCases = []struct { 25 | logColor Color 26 | outputColor Color 27 | successColor Color 28 | infoColor Color 29 | errorColor Color 30 | warnColor Color 31 | runningColor Color 32 | askColor Color 33 | responseColor Color 34 | }{ 35 | {None, Blue, Green, Red, Yellow, Cyan, Magenta, White, Black}, 36 | {None, None, None, None, None, None, None, None, None}, 37 | {BrightBlue, BrightGreen, BrightRed, BrightYellow, BrightCyan, BrightMagenta, BrightWhite, BrightBlack, None}, 38 | } 39 | 40 | var addPrefixCases = []struct { 41 | ask string 42 | err string 43 | inf string 44 | log string 45 | out string 46 | suc string 47 | run string 48 | war string 49 | }{ 50 | {"", "", "", "", "", "", "", ""}, 51 | {" ", " ", " ", " ", " ", " ", " ", " "}, 52 | {" ", " ", " ", " ", " ", " ", " ", " "}, 53 | {Cross, Check, "!", "~", "@", "#", "+", "="}, 54 | {"%", "^", "&", "*", "@", ":", ",", "?"}, 55 | } 56 | 57 | var trimCases = []struct { 58 | input string 59 | trim string 60 | }{ 61 | {" 123 \r\n", " "}, 62 | {" 123 \r\n", " "}, 63 | {"!!123!!\r\n", "!"}, 64 | {" !!123!! \r\n", "! "}, 65 | } 66 | 67 | func Example() { 68 | var ui UI 69 | reader := strings.NewReader("User Input\r\n") // Simulate user typing "User Input" then pressing [enter] when reading from os.Stdin 70 | ui = New(reader, os.Stdout, os.Stdout) 71 | ui = AddPrefix("?", Cross, " ", "", "", "~", Check, "!", ui) 72 | ui = AddConcurrent(ui) 73 | 74 | _, err := ui.Ask("Ask question", " ") 75 | if err != nil { 76 | fmt.Println(err) 77 | } 78 | ui.Error("Error message") 79 | ui.Info("Info message") 80 | ui.Output("Output message") 81 | ui.Running("Running message") 82 | ui.Success("Success message") 83 | ui.Warn("Warning message") 84 | 85 | // Output: 86 | // ? Ask question 87 | // ✗ Error message 88 | // Info message 89 | // Output message 90 | // ~ Running message 91 | // ✓ Success message 92 | // ! Warning message 93 | } 94 | 95 | func TestNew(t *testing.T) { 96 | assert := assert.New(t) 97 | for _, c := range newCases { 98 | basic := New(c.in, c.out, c.err) 99 | assert.Equal(c.in, basic.Reader) 100 | assert.Equal(c.out, basic.Writer) 101 | assert.Equal(c.err, basic.ErrorWriter) 102 | } 103 | } 104 | 105 | func TestAskTrim(t *testing.T) { 106 | assert := assert.New(t) 107 | for _, c := range trimCases { 108 | writer, errWriter, in := initTest(c.input) 109 | basic := New(in, writer, errWriter) 110 | res, err := basic.Ask("Awesome string", c.trim) 111 | if err != nil { 112 | assert.Fail(err.Error()) 113 | } 114 | out, err := writer.ReadString((byte)('\n')) 115 | if err != nil { 116 | assert.Fail(err.Error()) 117 | } 118 | _, err = errWriter.ReadString((byte)('\n')) 119 | expectedString := "Awesome string\n" 120 | assert.Equal("EOF", err.Error()) 121 | assert.Equal(expectedString, out) 122 | expectedAns := strings.Replace(c.input, "\r", "", -1) 123 | expectedAns = strings.Replace(expectedAns, "\n", "", -1) 124 | expectedAns = strings.Trim(expectedAns, c.trim) 125 | assert.Equal(expectedAns, res) 126 | } 127 | } 128 | 129 | func TestAddColor(t *testing.T) { 130 | assert := assert.New(t) 131 | basic := New(os.Stdin, os.Stdout, os.Stderr) 132 | for _, c := range addColorCases { 133 | color := AddColor(c.askColor, c.errorColor, c.infoColor, c.logColor, c.outputColor, c.responseColor, c.runningColor, c.successColor, c.warnColor, basic) 134 | assert.Equal(None, color.AskBGColor) 135 | assert.Equal(None, color.ErrorBGColor) 136 | assert.Equal(None, color.InfoBGColor) 137 | assert.Equal(None, color.LogBGColor) 138 | assert.Equal(None, color.OutputBGColor) 139 | assert.Equal(None, color.ResponseBGColor) 140 | assert.Equal(None, color.RunningBGColor) 141 | assert.Equal(None, color.SuccessBGColor) 142 | assert.Equal(None, color.WarnBGColor) 143 | assert.Equal(c.askColor, color.AskFGColor) 144 | assert.Equal(c.errorColor, color.ErrorFGColor) 145 | assert.Equal(c.infoColor, color.InfoFGColor) 146 | assert.Equal(c.logColor, color.LogFGColor) 147 | assert.Equal(c.outputColor, color.OutputFGColor) 148 | assert.Equal(c.responseColor, color.ResponseFGColor) 149 | assert.Equal(c.runningColor, color.RunningFGColor) 150 | assert.Equal(c.successColor, color.SuccessFGColor) 151 | assert.Equal(c.warnColor, color.WarnFGColor) 152 | assert.Equal(basic, color.UI) 153 | } 154 | } 155 | 156 | func TestAddPrefix(t *testing.T) { 157 | assert := assert.New(t) 158 | basic := New(os.Stdin, os.Stdout, os.Stderr) 159 | for _, c := range addPrefixCases { 160 | prefix := AddPrefix(c.ask, c.err, c.inf, c.log, c.out, c.run, c.suc, c.war, basic) 161 | assert.Equal(c.ask, prefix.AskPrefix) 162 | assert.Equal(c.err, prefix.ErrorPrefix) 163 | assert.Equal(c.inf, prefix.InfoPrefix) 164 | assert.Equal(c.log, prefix.LogPrefix) 165 | assert.Equal(c.out, prefix.OutputPrefix) 166 | assert.Equal(c.run, prefix.RunningPrefix) 167 | assert.Equal(c.suc, prefix.SuccessPrefix) 168 | assert.Equal(c.war, prefix.WarnPrefix) 169 | assert.Equal(basic, prefix.UI) 170 | } 171 | } 172 | 173 | func TestAddConcurrent(t *testing.T) { 174 | basic := New(os.Stdin, os.Stdout, os.Stderr) 175 | con := AddConcurrent(basic) 176 | assert.Equal(t, basic, con.UI) 177 | } 178 | 179 | func initTest(input string) (*bytes.Buffer, *bytes.Buffer, io.Reader) { 180 | var b []byte 181 | var e []byte 182 | return bytes.NewBuffer(b), bytes.NewBuffer(e), strings.NewReader(input) 183 | } 184 | --------------------------------------------------------------------------------