├── .github └── workflows │ └── go.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README_EN.md ├── codecov.yml ├── console ├── console.go ├── console_test.go ├── default.go ├── default_test.go ├── level.go └── option.go ├── example └── main.go ├── go.mod ├── go.sum ├── logger.go ├── resource ├── idea1.png ├── idea2.png └── terminal.png └── text ├── border.go ├── border_test.go ├── frame.go ├── frame_test.go ├── option.go ├── row.go └── row_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Go 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.17 19 | 20 | - name: Build 21 | run: go build -v ./... 22 | 23 | - name: Test 24 | run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... 25 | 26 | - uses: codecov/codecov-action@v2 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.10.x 5 | - tip 6 | 7 | before_install: 8 | - go get -t -v ./... 9 | 10 | script: 11 | - go test -race -coverprofile=coverage.txt -covermode=atomic 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 anqiansong 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 | # Ketty 2 | 3 | [![Go](https://github.com/anqiansong/ketty/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/anqiansong/ketty/actions/workflows/go.yml) 4 | [![codecov](https://codecov.io/gh/anqiansong/ketty/branch/main/graph/badge.svg?token=KAMZX9OEYF)](https://codecov.io/gh/anqiansong/ketty) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/anqiansong/ketty)](https://goreportcard.com/report/github.com/anqiansong/ketty) 6 | [![License](https://img.shields.io/github/license/anqiansong/ketty)](https://github.com/anqiansong/ketty/blob/main/LICENSE) 7 | 8 | 中文|[English](README_EN.md) 9 | 10 | ketty 是一个Golang 开发的简单的日志美化输出 Logger。 11 | 12 | ## 安装 13 | 14 | ```bash 15 | $ go install github.com/anqiansong/ketty@latest 16 | ``` 17 | 18 | ## 快速开始 19 | 20 | 默认 console 是输出到控制台的,如需要将文件存储到磁盘,请参考下文日志持久化 21 | 22 | ```go 23 | func main(){ 24 | console.Info(` 25 | { 26 | "name":"Hello Ketty", 27 | "description":"a color logger", 28 | "author":"anqiansong", 29 | "category":"console", 30 | "github":"https://github.com/anqiansong/ketty", 31 | "useage":[ 32 | "info", 33 | "debug" 34 | ] 35 | }`) 36 | console.Debug("Hello Ketty") 37 | console.Warn("Hello Ketty") 38 | console.Error(errors.New("error test")) 39 | } 40 | ``` 41 | 42 | ## 终端显示 43 | 44 | ![terminal](./resource/terminal.png) 45 | 46 | ## Goland 显示 47 | 48 | ![idea1](./resource/idea1.png) 49 | ![idea1](./resource/idea2.png) 50 | 51 | ## 用法 52 | 53 | ### 直接使用 54 | 55 | 直接使用的 Console 实例支持一些默认配置项: 56 | 57 | * 使用 `frame.WithLineStyle` 作为边框 58 | * 默认美化日志 59 | * 不会持久化到文件 60 | 61 | ```go 62 | func main(){ 63 | console.Info("Hello ketty, This is a info log") 64 | console.Debug("Hello ketty, This is a debug log") 65 | console.Warn("Hello ketty, This is a warn log") 66 | console.Error(errors.New("Hello ketty,This is an error")) 67 | } 68 | ``` 69 | 70 | ### 初始化 71 | 72 | ```go 73 | // 替换默认的边框 74 | plusStyle := text.WithPlusStyle() 75 | c := console.NewConsole(console.WithTextOption(plusStyle)) 76 | ``` 77 | 78 | ### Console 配置 79 | 80 | ```go 81 | c.DisableBorder() // 禁用边框 82 | c.DisableColor() // 禁用颜色美化 83 | ``` 84 | 85 | ### 打印 log 86 | 87 | ```go 88 | // 输出 info 日志 89 | c.Info("Hello Ketty, It's now %q", time.Now()) 90 | ``` 91 | 92 | ## 边框样式 93 | 94 | ### 预设样式 95 | 96 | * WithLineStyle 默认样式 97 | 98 | ```text 99 | [INFO] 2021-11-26 22:29:14.508 ┌──────────────────────────────────────────────────────────────────────────── 100 | [INFO] 2021-11-26 22:29:14.508 │ Hello Ketty, It's now "2021-11-26 22:29:14.508085 +0800 CST m=+0.000229190" 101 | [INFO] 2021-11-26 22:29:14.508 └──────────────────────────────────────────────────────────────────────────── 102 | ``` 103 | 104 | * WithDotStyle 105 | 106 | ```text 107 | [INFO] 2021-11-26 22:30:22.913 ............................................................................. 108 | [INFO] 2021-11-26 22:30:22.913 . Hello Ketty, It's now "2021-11-26 22:30:22.913678 +0800 CST m=+0.000199931" 109 | [INFO] 2021-11-26 22:30:22.913 ............................................................................. 110 | ``` 111 | 112 | * WithStarStyle 113 | 114 | ```text 115 | [INFO] 2021-11-26 22:31:00.699 ***************************************************************************** 116 | [INFO] 2021-11-26 22:31:00.699 * Hello Ketty, It's now "2021-11-26 22:31:00.699094 +0800 CST m=+0.000186578" 117 | [INFO] 2021-11-26 22:31:00.699 ***************************************************************************** 118 | ``` 119 | 120 | * WithPlusStyle 121 | 122 | ```text 123 | [INFO] 2021-11-26 22:31:26.952 +---------------------------------------------------------------------------- 124 | [INFO] 2021-11-26 22:31:26.952 | Hello Ketty, It's now "2021-11-26 22:31:26.952376 +0800 CST m=+0.000168647" 125 | [INFO] 2021-11-26 22:31:26.952 +---------------------------------------------------------------------------- 126 | ``` 127 | 128 | * WithFivePointedStarStyle 129 | 130 | ```text 131 | [INFO] 2021-11-26 22:31:58.146 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 132 | [INFO] 2021-11-26 22:31:58.146 ★ Hello Ketty, It's now "2021-11-26 22:31:58.146534 +0800 CST m=+0.000171850" 133 | [INFO] 2021-11-26 22:31:58.146 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 134 | ``` 135 | 136 | * WithDoubleLine 137 | 138 | ```text 139 | [INFO] 2021-11-26 22:32:21.574 ╔════════════════════════════════════════════════════════════════════════════ 140 | [INFO] 2021-11-26 22:32:21.574 ║ Hello Ketty, It's now "2021-11-26 22:32:21.573911 +0800 CST m=+0.000152572" 141 | [INFO] 2021-11-26 22:32:21.574 ╚════════════════════════════════════════════════════════════════════════════ 142 | ``` 143 | 144 | * DisableBorder 145 | 146 | ```text 147 | [INFO] 2021-11-26 22:33:01.695 Hello Ketty, It's now "2021-11-26 22:33:01.695338 +0800 CST m=+0.000156150" 148 | ``` 149 | 150 | ### 自定义样式 151 | 152 | * WithCommonBorder 153 | 154 | ```go 155 | // 边框横向、众项、拐角均为一种符号 156 | plusStyle := text.WithCommonBorder("x") 157 | ``` 158 | 159 | ```text 160 | [INFO] 2021-11-26 22:34:01.437 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 161 | [INFO] 2021-11-26 22:34:01.437 x Hello Ketty, It's now "2021-11-26 22:34:01.437286 +0800 CST m=+0.000153825" 162 | [INFO] 2021-11-26 22:34:01.437 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 163 | ``` 164 | 165 | * WithBorder 166 | 167 | ```go 168 | // arg1: 左上角符号 169 | // arg2: 左下角符号 170 | // arg3: 横向边框符号 171 | // arg4: 垂直边框符号 172 | plusStyle := text.WithBorder("=", "=", "-", "|") 173 | c := console.NewConsole(console.WithTextOption(plusStyle)) 174 | ``` 175 | 176 | ```text 177 | [INFO] 2021-11-26 22:37:38.409 =---------------------------------------------------------------------------- 178 | [INFO] 2021-11-26 22:37:38.409 | Hello Ketty, It's now "2021-11-26 22:37:38.408952 +0800 CST m=+0.000155037" 179 | [INFO] 2021-11-26 22:37:38.409 =---------------------------------------------------------------------------- 180 | ``` 181 | 182 | ## 日志持久化 183 | 184 | 你可以通过 WithOutputDir 指定一个日志输出目录,并通过 Flush 进行日志强行写入磁盘,默认情况下, ketty 会每秒钟自动去 Flush 一次。 185 | 186 | ```go 187 | c := NewConsole(WithOutputDir(dir)) 188 | // Don't forget to close it, otherwise the goroutine maybe overflow 189 | defer c.Close() 190 | // 为了防止日志文件急剧增大,可以关闭颜色美化和边框美化,减少不必要的日志输出到文件 191 | c.DisableColor() 192 | c.DisableBorder() 193 | c.Info("It's now: %v", time.Now()) 194 | 195 | // 手动落盘 196 | c.Flush() 197 | ``` 198 | 199 | ## 注意事项 200 | 201 | Windows 不支持美化输出。 -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Ketty 2 | [![Go](https://github.com/anqiansong/ketty/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/anqiansong/ketty/actions/workflows/go.yml) 3 | [![codecov](https://codecov.io/gh/anqiansong/ketty/branch/main/graph/badge.svg?token=KAMZX9OEYF)](https://codecov.io/gh/anqiansong/ketty) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/anqiansong/ketty)](https://goreportcard.com/report/github.com/anqiansong/ketty) 5 | [![License](https://img.shields.io/github/license/anqiansong/ketty)](https://github.com/anqiansong/ketty/blob/main/LICENSE) 6 | 7 | 8 | [中文](README.md)|English 9 | 10 | ketty is a Logger output with pretty and colorful log of Golang 。 11 | 12 | ## Installation 13 | 14 | ```bash 15 | $ go install github.com/anqiansong/ketty@latest 16 | ``` 17 | 18 | ## Quick Start 19 | The default console is output to the console, if you need to store the file to disk, 20 | please refer to Log `Persistence` below 21 | 22 | ```go 23 | func main(){ 24 | console.Info(` 25 | { 26 | "name":"Hello Ketty", 27 | "description":"a color logger", 28 | "author":"anqiansong", 29 | "category":"console", 30 | "github":"https://github.com/anqiansong/ketty", 31 | "useage":[ 32 | "info", 33 | "debug" 34 | ] 35 | }`) 36 | console.Debug("Hello Ketty") 37 | console.Warn("Hello Ketty") 38 | console.Error(errors.New("error test")) 39 | } 40 | ``` 41 | 42 | ## Output 43 | 44 | ### Terminal 45 | ![terminal](./resource/terminal.png) 46 | 47 | ### Idea 48 | ![idea1](./resource/idea1.png) 49 | ![idea1](./resource/idea2.png) 50 | 51 | ## Usage 52 | ### Use Directly 53 | There are some default options if you use directly: 54 | * the border style is `frame.WithLineStyle` 55 | * colorful output is opened by default 56 | 57 | ```go 58 | func main(){ 59 | console.Info("Hello ketty, This is a info log") 60 | console.Debug("Hello ketty, This is a debug log") 61 | console.Warn("Hello ketty, This is a warn log") 62 | console.Error(errors.New("Hello ketty,This is an error")) 63 | } 64 | ``` 65 | 66 | ### Initialization 67 | ```go 68 | // Use Plus-Style as border. 69 | plusStyle := text.WithPlusStyle() 70 | c := console.NewConsole(console.WithTextOption(plusStyle)) 71 | ``` 72 | 73 | ### Console Configure 74 | ```go 75 | c.DisableBorder() // disable border 76 | c.DisableColor() // disable colorful output 77 | ``` 78 | 79 | ### Print 80 | ```go 81 | // Print info log 82 | c.Info("Hello Ketty, It's now %q", time.Now()) 83 | ``` 84 | 85 | ## Border 86 | ### Preset 87 | * WithLineStyle Defaul 88 | ```text 89 | [INFO] 2021-11-26 22:29:14.508 ┌──────────────────────────────────────────────────────────────────────────── 90 | [INFO] 2021-11-26 22:29:14.508 │ Hello Ketty, It's now "2021-11-26 22:29:14.508085 +0800 CST m=+0.000229190" 91 | [INFO] 2021-11-26 22:29:14.508 └──────────────────────────────────────────────────────────────────────────── 92 | ``` 93 | * WithDotStyle 94 | ```text 95 | [INFO] 2021-11-26 22:30:22.913 ............................................................................. 96 | [INFO] 2021-11-26 22:30:22.913 . Hello Ketty, It's now "2021-11-26 22:30:22.913678 +0800 CST m=+0.000199931" 97 | [INFO] 2021-11-26 22:30:22.913 ............................................................................. 98 | ``` 99 | 100 | * WithStarStyle 101 | ```text 102 | [INFO] 2021-11-26 22:31:00.699 ***************************************************************************** 103 | [INFO] 2021-11-26 22:31:00.699 * Hello Ketty, It's now "2021-11-26 22:31:00.699094 +0800 CST m=+0.000186578" 104 | [INFO] 2021-11-26 22:31:00.699 ***************************************************************************** 105 | ``` 106 | 107 | * WithPlusStyle 108 | ```text 109 | [INFO] 2021-11-26 22:31:26.952 +---------------------------------------------------------------------------- 110 | [INFO] 2021-11-26 22:31:26.952 | Hello Ketty, It's now "2021-11-26 22:31:26.952376 +0800 CST m=+0.000168647" 111 | [INFO] 2021-11-26 22:31:26.952 +---------------------------------------------------------------------------- 112 | ``` 113 | 114 | * WithFivePointedStarStyle 115 | ```text 116 | [INFO] 2021-11-26 22:31:58.146 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 117 | [INFO] 2021-11-26 22:31:58.146 ★ Hello Ketty, It's now "2021-11-26 22:31:58.146534 +0800 CST m=+0.000171850" 118 | [INFO] 2021-11-26 22:31:58.146 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 119 | ``` 120 | 121 | * WithDoubleLine 122 | ```text 123 | [INFO] 2021-11-26 22:32:21.574 ╔════════════════════════════════════════════════════════════════════════════ 124 | [INFO] 2021-11-26 22:32:21.574 ║ Hello Ketty, It's now "2021-11-26 22:32:21.573911 +0800 CST m=+0.000152572" 125 | [INFO] 2021-11-26 22:32:21.574 ╚════════════════════════════════════════════════════════════════════════════ 126 | ``` 127 | 128 | * DisableBorder 129 | ```text 130 | [INFO] 2021-11-26 22:33:01.695 Hello Ketty, It's now "2021-11-26 22:33:01.695338 +0800 CST m=+0.000156150" 131 | ``` 132 | 133 | ### Custom Style 134 | * WithCommonBorder 135 | ```go 136 | // The all direction use the same character 137 | plusStyle := text.WithCommonBorder("x") 138 | ``` 139 | ```text 140 | [INFO] 2021-11-26 22:34:01.437 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 141 | [INFO] 2021-11-26 22:34:01.437 x Hello Ketty, It's now "2021-11-26 22:34:01.437286 +0800 CST m=+0.000153825" 142 | [INFO] 2021-11-26 22:34:01.437 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 143 | ``` 144 | 145 | * WithBorder 146 | ```go 147 | // arg1: the character in the left-top 148 | // arg2: the character in the left-bottom 149 | // arg3: the character in horizontal 150 | // arg4: the character in vertical 151 | plusStyle := text.WithBorder("=","=","-","|") 152 | c := console.NewConsole(console.WithTextOption(plusStyle)) 153 | ``` 154 | ```text 155 | [INFO] 2021-11-26 22:37:38.409 =---------------------------------------------------------------------------- 156 | [INFO] 2021-11-26 22:37:38.409 | Hello Ketty, It's now "2021-11-26 22:37:38.408952 +0800 CST m=+0.000155037" 157 | [INFO] 2021-11-26 22:37:38.409 =---------------------------------------------------------------------------- 158 | ``` 159 | 160 | ## Persistence 161 | You can specify a log output directory with `WithOutputDir` and force the logs to disk with `Flush`, by default ketty 162 | will automatically go to Flush once every second. 163 | 164 | ```go 165 | c := NewConsole(WithOutputDir(dir)) 166 | // Don't forget to close it, otherwise the goroutine maybe overflow 167 | defer c.Close() 168 | // To prevent the log file from growing dramatically, you can turn off color 169 | // and border beautification to reduce unnecessary log output to the file 170 | c.DisableColor() 171 | c.DisableBorder() 172 | c.Info("It's now: %v", time.Now()) 173 | 174 | // Manually drop the tray 175 | c.Flush() 176 | ``` 177 | 178 | Translated with www.DeepL.com/Translator (free version) 179 | 180 | ## Notes 181 | The colorful output does not support on Windows. 182 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /console/console.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package console 24 | 25 | import ( 26 | "bufio" 27 | "fmt" 28 | "os" 29 | "runtime" 30 | "sync" 31 | "time" 32 | 33 | "github.com/anqiansong/ketty" 34 | "github.com/anqiansong/ketty/text" 35 | "github.com/logrusorgru/aurora" 36 | "github.com/pkg/errors" 37 | ) 38 | 39 | const dateFormat = `2006-01-02 15:04:05.999` 40 | 41 | // Option is an alias of the function with argument Console. 42 | type Option func(c *Console) 43 | 44 | var _ ketty.Logger = (*Console)(nil) 45 | 46 | // Console is log printer which implements Logger. 47 | type Console struct { 48 | useColor bool 49 | usePrefix bool 50 | opt []text.Option 51 | output string 52 | testTime string 53 | prefix string 54 | w *bufio.Writer 55 | ticker *time.Ticker 56 | lock sync.Mutex 57 | once sync.Once 58 | doneChan chan struct{} 59 | level Level 60 | } 61 | 62 | // NewConsole creates an instance of Console. 63 | func NewConsole(opt ...Option) *Console { 64 | c := &Console{ 65 | useColor: true, 66 | usePrefix: true, 67 | level: LevelDebug, 68 | } 69 | for _, o := range opt { 70 | o(c) 71 | } 72 | return c 73 | } 74 | 75 | // DisableColor sets the color flag as false, if it is, 76 | // the Console won't print log with color. 77 | func (c *Console) DisableColor() { 78 | c.useColor = false 79 | } 80 | 81 | // DisablePrefix prints log without prefix 82 | func (c *Console) DisablePrefix() { 83 | c.usePrefix = false 84 | } 85 | 86 | // DisableBorder prints log with borderless. 87 | func (c *Console) DisableBorder() { 88 | c.opt = append(c.opt, text.DisableBorder()) 89 | } 90 | 91 | func (c *Console) useTestTime(t string) { 92 | c.testTime = t 93 | } 94 | 95 | // SetLevel set log level. 96 | func (c *Console) SetLevel(level Level) { 97 | c.level = level 98 | } 99 | 100 | // Disable prevent log to output. 101 | func (c *Console) Disable() { 102 | c.level = LevelDisable 103 | } 104 | 105 | func (c *Console) now() string { 106 | now := time.Now().Format(dateFormat) 107 | if len(c.testTime) > 0 { 108 | now = c.testTime 109 | } 110 | return now 111 | } 112 | 113 | // Info prints info level log. 114 | func (c *Console) Info(format string, v ...interface{}) { 115 | if c.level < LevelInfo { 116 | return 117 | } 118 | msg := fmt.Sprintf(format, v...) 119 | var opt []text.Option 120 | if c.usePrefix { 121 | if len(c.prefix) > 0 { 122 | opt = []text.Option{text.WithPrefix(c.prefix)} 123 | } else { 124 | opt = []text.Option{text.WithPrefix("[INFO] ", c.now())} 125 | } 126 | } 127 | opt = append(opt, c.opt...) 128 | output := text.Convert(msg, opt...) 129 | if runtime.GOOS != "windows" && c.useColor { 130 | output = aurora.Green(output).String() 131 | } 132 | c.fPrintf(output) 133 | } 134 | 135 | // Debug prints debug level log. 136 | func (c *Console) Debug(format string, v ...interface{}) { 137 | if c.level < LevelDebug { 138 | return 139 | } 140 | msg := fmt.Sprintf(format, v...) 141 | var opt []text.Option 142 | if c.usePrefix { 143 | if len(c.prefix) > 0 { 144 | opt = []text.Option{text.WithPrefix(c.prefix)} 145 | } else { 146 | opt = []text.Option{text.WithPrefix("[DEBUG] ", c.now())} 147 | } 148 | } 149 | opt = append(opt, c.opt...) 150 | output := text.Convert(msg, opt...) 151 | if runtime.GOOS != "windows" && c.useColor { 152 | output = aurora.Blue(output).String() 153 | } 154 | c.fPrintf(output) 155 | } 156 | 157 | // Warn prints warn level log. 158 | func (c *Console) Warn(format string, v ...interface{}) { 159 | if c.level < LevelWarn { 160 | return 161 | } 162 | msg := fmt.Sprintf(format, v...) 163 | var opt []text.Option 164 | if c.usePrefix { 165 | if len(c.prefix) > 0 { 166 | opt = []text.Option{text.WithPrefix(c.prefix)} 167 | } else { 168 | opt = []text.Option{text.WithPrefix("[WARN] ", c.now())} 169 | } 170 | } 171 | opt = append(opt, c.opt...) 172 | output := text.Convert(msg, opt...) 173 | if runtime.GOOS != "windows" && c.useColor { 174 | output = aurora.Yellow(output).String() 175 | } 176 | c.fPrintf(output) 177 | } 178 | 179 | // Error prints error level log. 180 | func (c *Console) Error(err error) { 181 | if c.level < LevelError { 182 | return 183 | } 184 | err = errors.WithStack(err) 185 | msg := fmt.Sprintf("%+v", err) 186 | var opt []text.Option 187 | if c.usePrefix { 188 | if len(c.prefix) > 0 { 189 | opt = []text.Option{text.WithPrefix(c.prefix)} 190 | } else { 191 | opt = []text.Option{text.WithPrefix("[ERROR] ", c.now())} 192 | } 193 | } 194 | opt = append(opt, c.opt...) 195 | output := text.Convert(msg, opt...) 196 | if runtime.GOOS != "windows" && c.useColor { 197 | output = aurora.Red(output).String() 198 | } 199 | c.fPrintf(output, true) 200 | } 201 | 202 | // ErrorText prints error level log. 203 | func (c *Console) ErrorText(format string, v ...interface{}) { 204 | if c.level < LevelError { 205 | return 206 | } 207 | msg := fmt.Sprintf(format, v...) 208 | var opt []text.Option 209 | if c.usePrefix { 210 | if len(c.prefix) > 0 { 211 | opt = []text.Option{text.WithPrefix(c.prefix)} 212 | } else { 213 | opt = []text.Option{text.WithPrefix("[ERROR] ", c.now())} 214 | } 215 | } 216 | opt = append(opt, c.opt...) 217 | output := text.Convert(msg, opt...) 218 | if runtime.GOOS != "windows" && c.useColor { 219 | output = aurora.Red(output).String() 220 | } 221 | c.fPrintf(output) 222 | } 223 | 224 | func (c *Console) fPrintf(msg string, err ...bool) { 225 | if c.w == nil { 226 | if len(err) > 0 && err[0] { 227 | _, _ = fmt.Fprint(os.Stderr, msg) 228 | } else { 229 | _, _ = fmt.Fprint(os.Stdout, msg) 230 | } 231 | return 232 | } 233 | _, _ = fmt.Fprint(c.w, msg) 234 | } 235 | 236 | // Use sets Option into Console. 237 | func (c *Console) Use(opt ...Option) { 238 | for _, o := range opt { 239 | o(c) 240 | } 241 | } 242 | 243 | // Flush saves the buffered data on disk file. 244 | func (c *Console) Flush() error { 245 | if c.w == nil { 246 | return nil 247 | } 248 | c.lock.Lock() 249 | defer c.lock.Unlock() 250 | return c.w.Flush() 251 | } 252 | 253 | // rotate makes a new log file in interval. 254 | func (c *Console) rotate() error { 255 | panic("implement me") 256 | } 257 | 258 | // Close for cleaning up. 259 | func (c *Console) Close() { 260 | c.once.Do(func() { 261 | _ = c.Flush() 262 | if c.ticker != nil { 263 | c.ticker.Stop() 264 | } 265 | if c.doneChan != nil { 266 | close(c.doneChan) 267 | } 268 | }) 269 | } 270 | 271 | func (c *Console) listenFile() { 272 | for { 273 | select { 274 | case <-c.ticker.C: 275 | _ = c.Flush() 276 | case <-c.doneChan: 277 | return 278 | default: 279 | 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /console/console_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package console 24 | 25 | import ( 26 | "errors" 27 | "testing" 28 | 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestNewConsole(t *testing.T) { 33 | t.Run("withoutOption", func(t *testing.T) { 34 | c := NewConsole() 35 | assert.True(t, c.useColor) 36 | assert.True(t, len(c.output) == 0) 37 | }) 38 | 39 | t.Run("withOption", func(t *testing.T) { 40 | dir := t.TempDir() 41 | c := NewConsole(WithOutputDir(dir)) 42 | defer c.Close() 43 | c.DisableColor() 44 | c.DisableBorder() 45 | assert.Equal(t, dir, c.output) 46 | }) 47 | } 48 | 49 | func TestConsole_DisableColor(t *testing.T) { 50 | c := NewConsole() 51 | c.DisableColor() 52 | assert.False(t, c.useColor) 53 | } 54 | 55 | func ExampleConsole_DisableBorder() { 56 | c := NewConsole() 57 | c.DisableBorder() 58 | c.DisableColor() 59 | c.useTestTime("2021-11-27") 60 | c.Info("hello ketty") 61 | // Output: 62 | // [INFO] 2021-11-27 hello ketty 63 | } 64 | 65 | func ExampleConsole_DisablePrefix() { 66 | c := NewConsole() 67 | c.DisableBorder() 68 | c.DisableColor() 69 | c.DisablePrefix() 70 | c.Info("hello ketty") 71 | // Output: 72 | // hello ketty 73 | } 74 | 75 | func ExampleConsole_Info() { 76 | c := NewConsole() 77 | c.DisableColor() 78 | c.useTestTime("2021-11-27") 79 | c.Info("hello ketty") 80 | // Output: 81 | // [INFO] 2021-11-27┌──────────── 82 | // [INFO] 2021-11-27│ hello ketty 83 | // [INFO] 2021-11-27└──────────── 84 | } 85 | 86 | func ExampleConsole_Debug() { 87 | c := NewConsole() 88 | c.DisableColor() 89 | c.useTestTime("2021-11-27") 90 | c.Debug("hello ketty") 91 | // Output: 92 | // [DEBUG] 2021-11-27┌──────────── 93 | // [DEBUG] 2021-11-27│ hello ketty 94 | // [DEBUG] 2021-11-27└──────────── 95 | } 96 | 97 | func ExampleConsole_Warn() { 98 | c := NewConsole() 99 | c.DisableColor() 100 | c.useTestTime("2021-11-27") 101 | c.Warn("hello ketty") 102 | // Output: 103 | // [WARN] 2021-11-27┌──────────── 104 | // [WARN] 2021-11-27│ hello ketty 105 | // [WARN] 2021-11-27└──────────── 106 | } 107 | 108 | func TestConsole_Error(t *testing.T) { 109 | c := NewConsole() 110 | c.DisableColor() 111 | c.useTestTime("2021-11-27") 112 | c.Error(errors.New("hello ketty")) 113 | } 114 | 115 | func ExampleConsole_ErrorText() { 116 | c := NewConsole() 117 | c.DisableColor() 118 | c.DisablePrefix() 119 | c.DisableBorder() 120 | c.ErrorText("hello ketty") 121 | // output: 122 | // hello ketty 123 | } 124 | 125 | func ExampleUsePrefix() { 126 | c := NewConsole() 127 | c.prefix = "test" 128 | c.DisableBorder() 129 | c.DisableColor() 130 | c.Info("hello ketty") 131 | // output: 132 | // test hello ketty 133 | } 134 | -------------------------------------------------------------------------------- /console/default.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package console 24 | 25 | // DisableColor sets the color flag as false, if it is, 26 | // the Console won't print log with color. 27 | func DisableColor() { 28 | colorConsole.DisableColor() 29 | } 30 | 31 | // DisablePrefix prints log without prefix 32 | func DisablePrefix() { 33 | colorConsole.DisablePrefix() 34 | } 35 | 36 | func SetOutput(dir string) { 37 | colorConsole.Use(WithOutputDir(dir)) 38 | } 39 | 40 | // UsePrefix add a prefix to logger. 41 | func UsePrefix(prefix string) { 42 | colorConsole.prefix = prefix 43 | } 44 | 45 | // Close for cleaning up. 46 | func Close() { 47 | colorConsole.Close() 48 | } 49 | 50 | // DisableBorder prints log with borderless. 51 | func DisableBorder() { 52 | colorConsole.DisableBorder() 53 | } 54 | 55 | // GetConsole returns a Console instance. 56 | func GetConsole() *Console { 57 | return colorConsole 58 | } 59 | 60 | var colorConsole = NewConsole() 61 | 62 | // Info prints info level log. 63 | func Info(format string, v ...interface{}) { 64 | colorConsole.Info(format, v...) 65 | } 66 | 67 | // Debug prints debug level log. 68 | func Debug(format string, v ...interface{}) { 69 | colorConsole.Debug(format, v...) 70 | } 71 | 72 | // Warn prints warn level log. 73 | func Warn(format string, v ...interface{}) { 74 | colorConsole.Warn(format, v...) 75 | } 76 | 77 | // Error prints error level log. 78 | func Error(err error) { 79 | colorConsole.Error(err) 80 | } 81 | 82 | // ErrorText prints error level log. 83 | func ErrorText(format string, v ...interface{}) { 84 | colorConsole.ErrorText(format, v...) 85 | } 86 | -------------------------------------------------------------------------------- /console/default_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package console 24 | 25 | import ( 26 | "errors" 27 | "testing" 28 | 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestDisableColor(t *testing.T) { 33 | DisableColor() 34 | assert.False(t, colorConsole.useColor) 35 | } 36 | 37 | func ExampleDisableBorder() { 38 | DisableBorder() 39 | DisableColor() 40 | colorConsole.useTestTime("2021-11-27") 41 | Info("hello ketty") 42 | // Output: 43 | // [INFO] 2021-11-27 hello ketty 44 | } 45 | 46 | func ExampleInfo() { 47 | DisableColor() 48 | colorConsole.useTestTime("2021-11-27") 49 | Info("hello ketty") 50 | // Output: 51 | // [INFO] 2021-11-27 hello ketty 52 | } 53 | 54 | func ExampleDebug() { 55 | DisableColor() 56 | colorConsole.useTestTime("2021-11-27") 57 | Debug("hello ketty") 58 | // Output: 59 | // [DEBUG] 2021-11-27 hello ketty 60 | } 61 | 62 | func ExampleWarn() { 63 | DisableColor() 64 | colorConsole.useTestTime("2021-11-27") 65 | Warn("hello ketty") 66 | // Output: 67 | // [WARN] 2021-11-27 hello ketty 68 | } 69 | 70 | func ExampleErrorText() { 71 | DisableColor() 72 | DisablePrefix() 73 | DisableBorder() 74 | ErrorText("hello ketty") 75 | // Output: 76 | // hello ketty 77 | } 78 | 79 | func Test_WithPrefix(t *testing.T) { 80 | DisableColor() 81 | DisableBorder() 82 | WithPrefix("test") 83 | 84 | } 85 | 86 | func Test_Error(t *testing.T) { 87 | DisableColor() 88 | colorConsole.useTestTime("2021-11-27") 89 | Error(errors.New("hello ketty")) 90 | } 91 | -------------------------------------------------------------------------------- /console/level.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package console 24 | 25 | type Level int 26 | 27 | const ( 28 | _ Level = iota << 1 29 | LevelDisable 30 | LevelError 31 | LevelWarn 32 | LevelInfo 33 | LevelDebug 34 | ) 35 | -------------------------------------------------------------------------------- /console/option.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package console 24 | 25 | import ( 26 | "bufio" 27 | "os" 28 | "path/filepath" 29 | "time" 30 | 31 | "github.com/anqiansong/ketty/text" 32 | ) 33 | 34 | // WithTextOption returns an Option wrapped text.Option. 35 | func WithTextOption(opt ...text.Option) Option { 36 | return func(c *Console) { 37 | c.opt = opt 38 | } 39 | } 40 | 41 | // WithOutputDir sets an output file for Logger. 42 | func WithOutputDir(file string) Option { 43 | return func(c *Console) { 44 | c.lock.Lock() 45 | defer c.lock.Unlock() 46 | if len(file) == 0 { 47 | return 48 | } 49 | info, err := os.Stat(file) 50 | if err != nil && os.IsNotExist(err) { 51 | err = os.MkdirAll(file, os.ModePerm) 52 | if err != nil { 53 | panic(err) 54 | } 55 | } else { 56 | if !info.IsDir() { 57 | panic("invalid dir: " + file) 58 | } 59 | } 60 | 61 | c.output = file 62 | filename := filepath.Join(c.output, "access.log") 63 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) 64 | if err != nil { 65 | panic(err) 66 | } 67 | c.w = bufio.NewWriter(f) 68 | c.ticker = time.NewTicker(time.Second) 69 | c.doneChan = make(chan struct{}) 70 | go c.listenFile() 71 | } 72 | } 73 | 74 | // WithPrefix sets a prefix for Logger. 75 | func WithPrefix(prefix string) Option { 76 | return func(c *Console) { 77 | c.prefix = prefix 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package main 24 | 25 | import ( 26 | "github.com/anqiansong/ketty/console" 27 | "github.com/anqiansong/ketty/text" 28 | ) 29 | 30 | func main() { 31 | // 快速开始 32 | // console.Info(` 33 | // { 34 | // "name":"Hello Ketty", 35 | // "description":"a color logger", 36 | // "author":"anqiansong", 37 | // "category":"console", 38 | // "github":"https://github.com/anqiansong/ketty", 39 | // "useage":[ 40 | // "info", 41 | // "debug" 42 | // ] 43 | // }`) 44 | // console.Debug("Hello Ketty") 45 | // console.Warn("Hello Ketty") 46 | // console.Error(errors.New("error test")) 47 | 48 | // 直接使用 49 | // console.Info("Hello ketty, This is info log") 50 | // console.Debug("Hello ketty, This debug log") 51 | // console.Warn("Hello ketty, This warn log") 52 | // console.Error(errors.New("Hello ketty,This is an error")) 53 | 54 | // 初始化 55 | plusStyle := text.WithBorder("=", "=", "-", "|") 56 | c := console.NewConsole(console.WithTextOption(plusStyle)) 57 | // c.DisableBorder() 58 | // c.DisableColor() 59 | c.Info("Hello Ketty") 60 | } 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/anqiansong/ketty 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/logrusorgru/aurora v2.0.3+incompatible 7 | github.com/pkg/errors v0.9.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.0 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | github.com/stretchr/objx v0.1.0 // indirect 14 | github.com/stretchr/testify v1.7.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 4 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 5 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 6 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 12 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 14 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package ketty 24 | 25 | // Logger describes a log printer which contains Info, 26 | // Debug, Warn, Error method. 27 | type Logger interface { 28 | Info(format string, v ...interface{}) 29 | Debug(format string, v ...interface{}) 30 | Warn(format string, v ...interface{}) 31 | Error(err error) 32 | ErrorText(format string, v ...interface{}) 33 | } 34 | -------------------------------------------------------------------------------- /resource/idea1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kesonan/ketty/dbaf2e277891fc60582d44549890c86eceb13061/resource/idea1.png -------------------------------------------------------------------------------- /resource/idea2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kesonan/ketty/dbaf2e277891fc60582d44549890c86eceb13061/resource/idea2.png -------------------------------------------------------------------------------- /resource/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kesonan/ketty/dbaf2e277891fc60582d44549890c86eceb13061/resource/terminal.png -------------------------------------------------------------------------------- /text/border.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | // Corner describes the character for left side corner. 26 | type Corner struct { 27 | leftTop string 28 | leftBottom string 29 | } 30 | 31 | // LineSplitter describes the splitter in horizontal and vertica direction. 32 | type LineSplitter struct { 33 | horizontalLine string 34 | verticalLine string 35 | } 36 | 37 | // BorderStyle is an interface which describes a border style 38 | // for log print. 39 | type BorderStyle interface { 40 | Corner() Corner 41 | LineSplitter() LineSplitter 42 | } 43 | 44 | // Border implements BorderStyle to receive a border style. 45 | type Border struct { 46 | c Corner 47 | l LineSplitter 48 | } 49 | 50 | // Corner is the corner of border. 51 | func (b *Border) Corner() Corner { 52 | return b.c 53 | } 54 | 55 | // LineSplitter describes a line splitter in horizontal and vertical. 56 | func (b *Border) LineSplitter() LineSplitter { 57 | return b.l 58 | } 59 | 60 | // NoneBorder implements BorderStyle represents borderless. 61 | type NoneBorder struct{} 62 | 63 | // Corner is the corner of border. 64 | func (b *NoneBorder) Corner() Corner { 65 | return Corner{} 66 | } 67 | 68 | // LineSplitter describes a line splitter in horizontal and vertical. 69 | func (b *NoneBorder) LineSplitter() LineSplitter { 70 | return LineSplitter{} 71 | } 72 | 73 | // WithLineStyle describes a line style for border. 74 | func WithLineStyle() Option { 75 | return WithBorder("┌", "└", "─", "│") 76 | } 77 | 78 | // WithDotStyle describes a dot style for border. 79 | func WithDotStyle() Option { 80 | return WithCommonBorder(".") 81 | } 82 | 83 | // WithStarStyle describes a star style for border. 84 | func WithStarStyle() Option { 85 | return WithCommonBorder("*") 86 | } 87 | 88 | // WithPlusStyle describes a plus style for border. 89 | func WithPlusStyle() Option { 90 | return WithBorder("+", "+", "-", "|") 91 | } 92 | 93 | // DisableBorder represents that is disable border. 94 | func DisableBorder() Option { 95 | return func(f *Frame) { 96 | f.border = &NoneBorder{} 97 | } 98 | } 99 | 100 | // WithFivePointedStarStyle describes a file-pointed star style for border. 101 | func WithFivePointedStarStyle() Option { 102 | return WithCommonBorder("★") 103 | } 104 | 105 | // WithDoubleLine describes a double line style for border. 106 | func WithDoubleLine() Option { 107 | return WithBorder("╔", "╚", "═", "║") 108 | } 109 | -------------------------------------------------------------------------------- /text/border_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | import ( 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestBorder_Corner(t *testing.T) { 32 | b := Border{} 33 | assert.Equal(t, Corner{}, b.Corner()) 34 | } 35 | 36 | func TestBorder_LineSplitter(t *testing.T) { 37 | b := Border{} 38 | assert.Equal(t, LineSplitter{}, b.LineSplitter()) 39 | } 40 | 41 | func TestNoneBorder_Corner(t *testing.T) { 42 | b := NoneBorder{} 43 | assert.Equal(t, Corner{}, b.Corner()) 44 | } 45 | 46 | func TestNoneBorder_LineSplitter(t *testing.T) { 47 | b := NoneBorder{} 48 | assert.Equal(t, LineSplitter{}, b.LineSplitter()) 49 | } 50 | 51 | func TestWithLineStyle(t *testing.T) { 52 | o := WithLineStyle() 53 | f := NewFrame(NewRows("hello ketty"), o) 54 | expected := Border{ 55 | c: Corner{ 56 | leftTop: "┌", 57 | leftBottom: "└", 58 | }, 59 | l: LineSplitter{ 60 | horizontalLine: "─", 61 | verticalLine: "│", 62 | }, 63 | } 64 | actual, ok := f.border.(*Border) 65 | assert.True(t, ok) 66 | assert.Equal(t, expected, *actual) 67 | } 68 | 69 | func TestWithDotStyle(t *testing.T) { 70 | o := WithDotStyle() 71 | f := NewFrame(NewRows("hello ketty"), o) 72 | expected := Border{ 73 | c: Corner{ 74 | leftTop: ".", 75 | leftBottom: ".", 76 | }, 77 | l: LineSplitter{ 78 | horizontalLine: ".", 79 | verticalLine: ".", 80 | }, 81 | } 82 | actual, ok := f.border.(*Border) 83 | assert.True(t, ok) 84 | assert.Equal(t, expected, *actual) 85 | } 86 | 87 | func TestWithStarStyle(t *testing.T) { 88 | o := WithStarStyle() 89 | f := NewFrame(NewRows("hello ketty"), o) 90 | expected := Border{ 91 | c: Corner{ 92 | leftTop: "*", 93 | leftBottom: "*", 94 | }, 95 | l: LineSplitter{ 96 | horizontalLine: "*", 97 | verticalLine: "*", 98 | }, 99 | } 100 | actual, ok := f.border.(*Border) 101 | assert.True(t, ok) 102 | assert.Equal(t, expected, *actual) 103 | } 104 | 105 | func TestWithPlusStyle(t *testing.T) { 106 | o := WithPlusStyle() 107 | f := NewFrame(NewRows("hello ketty"), o) 108 | expected := Border{ 109 | c: Corner{ 110 | leftTop: "+", 111 | leftBottom: "+", 112 | }, 113 | l: LineSplitter{ 114 | horizontalLine: "-", 115 | verticalLine: "|", 116 | }, 117 | } 118 | actual, ok := f.border.(*Border) 119 | assert.True(t, ok) 120 | assert.Equal(t, expected, *actual) 121 | } 122 | 123 | func TestDisableBorder(t *testing.T) { 124 | o := DisableBorder() 125 | f := NewFrame(NewRows("hello ketty"), o) 126 | expected := NoneBorder{} 127 | actual, ok := f.border.(*NoneBorder) 128 | assert.True(t, ok) 129 | assert.Equal(t, expected, *actual) 130 | } 131 | 132 | func TestWithFivePointedStarStyle(t *testing.T) { 133 | o := WithFivePointedStarStyle() 134 | f := NewFrame(NewRows("hello ketty"), o) 135 | expected := Border{ 136 | c: Corner{ 137 | leftTop: "★", 138 | leftBottom: "★", 139 | }, 140 | l: LineSplitter{ 141 | horizontalLine: "★", 142 | verticalLine: "★", 143 | }, 144 | } 145 | actual, ok := f.border.(*Border) 146 | assert.True(t, ok) 147 | assert.Equal(t, expected, *actual) 148 | } 149 | 150 | func TestWithDoubleLine(t *testing.T) { 151 | o := WithDoubleLine() 152 | f := NewFrame(NewRows("hello ketty"), o) 153 | expected := Border{ 154 | c: Corner{ 155 | leftTop: "╔", 156 | leftBottom: "╚", 157 | }, 158 | l: LineSplitter{ 159 | horizontalLine: "═", 160 | verticalLine: "║", 161 | }, 162 | } 163 | actual, ok := f.border.(*Border) 164 | assert.True(t, ok) 165 | assert.Equal(t, expected, *actual) 166 | } -------------------------------------------------------------------------------- /text/frame.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | import ( 26 | "bytes" 27 | "fmt" 28 | "strings" 29 | "unicode/utf8" 30 | ) 31 | 32 | // Frame is a text render with options, such as render with 33 | // border, or with prefix. 34 | type Frame struct { 35 | w *bytes.Buffer 36 | rows []*Row 37 | rowCount int 38 | maxWidth int 39 | border BorderStyle 40 | prefix []string 41 | } 42 | 43 | // Convert converts a plain text into another text with options. 44 | func Convert(text string, opt ...Option) string { 45 | f := NewFrame(NewRows(text), opt...) 46 | f.draw() 47 | return f.w.String() 48 | } 49 | 50 | // NewFrame creates an instance of Frame. 51 | func NewFrame(rows []*Row, opt ...Option) *Frame { 52 | f := &Frame{} 53 | options := []Option{WithLineStyle()} 54 | options = append(options, opt...) 55 | for _, o := range options { 56 | o(f) 57 | } 58 | 59 | var max int 60 | for _, r := range rows { 61 | if max < r.maxLength { 62 | max = r.maxLength 63 | } 64 | } 65 | 66 | f.w = bytes.NewBuffer(nil) 67 | f.rows = rows 68 | f.rowCount = len(rows) 69 | f.maxWidth = max 70 | return f 71 | } 72 | 73 | func (f *Frame) draw() { 74 | if len(f.rows) == 0 { 75 | return 76 | } 77 | f.drawTopBorder() 78 | f.drawRows() 79 | f.drawBottomBorder() 80 | } 81 | 82 | func (f *Frame) drawTopBorder() { 83 | switch f.border.(type) { 84 | case *NoneBorder: 85 | return 86 | } 87 | f.drawPrefix() 88 | f.w.WriteString(f.border.Corner().leftTop) 89 | f.drawSplitter() 90 | f.newLine() 91 | } 92 | 93 | func (f *Frame) drawPrefix() { 94 | for _, p := range f.prefix { 95 | f.w.WriteString(p) 96 | } 97 | } 98 | 99 | func (f *Frame) drawRows() { 100 | for index, r := range f.rows { 101 | f.drawText(r.lineText) 102 | if index < f.rowCount-1 { 103 | f.drawRowSplitter() 104 | } 105 | 106 | } 107 | } 108 | 109 | func (f *Frame) drawSplitter() { 110 | f.w.WriteString(f.drawHorizontalLine(f.maxWidth)) 111 | } 112 | 113 | func (f *Frame) drawRowSplitter() { 114 | f.drawPrefix() 115 | f.w.WriteString(f.border.LineSplitter().verticalLine) 116 | f.w.WriteString(f.drawHorizontalLine(f.maxWidth)) 117 | f.newLine() 118 | } 119 | 120 | func (f *Frame) drawText(texts []string) { 121 | for _, item := range texts { 122 | f.drawPrefix() 123 | f.w.WriteString(f.border.LineSplitter().verticalLine) 124 | switch f.border.(type) { 125 | case *NoneBorder: 126 | if len(f.prefix) > 0 { 127 | f.w.WriteString(" " + item) 128 | } else { 129 | f.w.WriteString(item) 130 | } 131 | default: 132 | f.w.WriteString(" " + item) 133 | 134 | } 135 | 136 | f.drawSpace(f.maxWidth - len(item)) 137 | f.newLine() 138 | } 139 | } 140 | 141 | func (f *Frame) drawSpace(count int) { 142 | for i := 0; i < count; i++ { 143 | f.w.WriteString(" ") 144 | } 145 | } 146 | 147 | func (f *Frame) drawBottomBorder() { 148 | switch f.border.(type) { 149 | case *NoneBorder: 150 | return 151 | } 152 | f.drawPrefix() 153 | f.w.WriteString(f.border.Corner().leftBottom) 154 | f.drawSplitter() 155 | f.newLine() 156 | } 157 | 158 | func (f *Frame) drawHorizontalLine(count int) string { 159 | if len(f.border.LineSplitter().horizontalLine) == 0 { 160 | return "" 161 | } 162 | 163 | var line []string 164 | for { 165 | if len(line)*utf8.RuneCountInString(f.border.LineSplitter().horizontalLine) > count { 166 | break 167 | } 168 | line = append(line, f.border.LineSplitter().horizontalLine) 169 | } 170 | return strings.Join(line, "") 171 | } 172 | 173 | func (f *Frame) newLine() { 174 | f.w.WriteRune('\n') 175 | } 176 | 177 | // Print prints a text on terminal. 178 | func (f *Frame) Print() { 179 | f.draw() 180 | fmt.Println(f.w.String()) 181 | } 182 | -------------------------------------------------------------------------------- /text/frame_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | import ( 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestNewRow(t *testing.T) { 32 | rows := NewRows(`{ 33 | "name": "k", 34 | "age": 20, 35 | "gender": "男" 36 | }`) 37 | f := NewFrame(rows, WithPrefix("[INFO] ", "2021-11-26 16:11 "), DisableBorder()) 38 | f.Print() 39 | } 40 | 41 | func TestConvert(t *testing.T) { 42 | actual := Convert("hello ketty", DisableBorder()) 43 | assert.Equal(t, "hello ketty\n", actual) 44 | } 45 | 46 | func Test_draw(t *testing.T) { 47 | f:=NewFrame(NewRows("hello ketty")) 48 | t.Run("drawSplitter", func(t *testing.T) { 49 | f.drawSplitter() 50 | }) 51 | 52 | t.Run("drawRowSplitter", func(t *testing.T) { 53 | f.drawRowSplitter() 54 | }) 55 | 56 | t.Run("drawText", func(t *testing.T) { 57 | f.drawText([]string{}) 58 | }) 59 | 60 | t.Run("drawPrefix", func(t *testing.T) { 61 | f.drawPrefix() 62 | }) 63 | 64 | t.Run("drawHorizontalLine", func(t *testing.T) { 65 | f.drawHorizontalLine(0) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /text/option.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | // Option is an alias of the function with argument Frame . 26 | type Option func(f *Frame) 27 | 28 | // WithBorder creates an Option from arguments. 29 | func WithBorder(leftTop, leftBottom, horizontal, vertical string) Option { 30 | border := &Border{ 31 | c: Corner{ 32 | leftTop: leftTop, 33 | leftBottom: leftBottom, 34 | }, 35 | l: LineSplitter{ 36 | horizontalLine: horizontal, 37 | verticalLine: vertical, 38 | }, 39 | } 40 | return func(f *Frame) { 41 | f.border = border 42 | } 43 | } 44 | 45 | // WithCommonBorder creates a Border Option which have the same character. 46 | func WithCommonBorder(char string) Option { 47 | border := &Border{ 48 | c: Corner{ 49 | leftTop: char, 50 | leftBottom: char, 51 | }, 52 | l: LineSplitter{ 53 | horizontalLine: char, 54 | verticalLine: char, 55 | }, 56 | } 57 | return func(f *Frame) { 58 | f.border = border 59 | } 60 | } 61 | 62 | // WithPrefix creates a prefix Option. 63 | func WithPrefix(prefix ...string) Option { 64 | return func(f *Frame) { 65 | f.prefix = prefix 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /text/row.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | import ( 26 | "strings" 27 | "unicode/utf8" 28 | ) 29 | 30 | // Row describes a log row. 31 | type Row struct { 32 | lineText []string 33 | maxLength int 34 | } 35 | 36 | // NewRows creates an instance of Row. 37 | func NewRows(text ...string) []*Row { 38 | var list []*Row 39 | for _, t := range text { 40 | texts := strings.Split(t, "\n") 41 | var max = 0 42 | for _, t := range texts { 43 | runeCount := utf8.RuneCountInString(t) 44 | if max < runeCount { 45 | max = runeCount 46 | } 47 | } 48 | list = append(list, &Row{ 49 | lineText: texts, 50 | maxLength: max, 51 | }) 52 | } 53 | return list 54 | } 55 | -------------------------------------------------------------------------------- /text/row_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2021 anqiansong 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 | 23 | package text 24 | 25 | import ( 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestNewRows(t *testing.T) { 32 | r := NewRows("hello ketty") 33 | assert.Equal(t, []string{"hello ketty"}, r[0].lineText) 34 | } 35 | --------------------------------------------------------------------------------