├── go.mod ├── .gitignore ├── icecream ├── icecream_test.go └── icecream.go ├── LICENSE ├── go.sum └── Readme.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/WAY29/icecream-go 2 | 3 | go 1.22.1 4 | 5 | require github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | github.com/stretchr/testify v1.9.0 // indirect 11 | gopkg.in/yaml.v3 v3.0.1 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | 5 | .idea/ 6 | 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.exe~ 10 | *.dll 11 | *.so 12 | *.dylib 13 | 14 | # Test binary, built with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Dependency directories (remove the comment below to include it) 21 | # vendor/ 22 | 23 | # Go workspace file 24 | go.work 25 | -------------------------------------------------------------------------------- /icecream/icecream_test.go: -------------------------------------------------------------------------------- 1 | package icecream 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestConfigureOutputFunction(t *testing.T) { 10 | builder := strings.Builder{} 11 | ok := ConfigureOutputFunction(&builder) 12 | assert.Equal(t, true, ok) 13 | 14 | expected := "111" 15 | printMsg(expected) 16 | actual := builder.String() 17 | assert.Equal(t, expected, actual) 18 | } 19 | 20 | func TestIc(t *testing.T) { 21 | builder := strings.Builder{} 22 | ok := ConfigureOutputFunction(&builder) 23 | assert.Equal(t, true, ok) 24 | ConfigureArgNameFormatterFunc(func(name string) string { 25 | return "\u001B[36m" + name + "\u001B[0m" 26 | }) 27 | 28 | str := "hello" 29 | Ic(str) 30 | actual := builder.String() 31 | assert.Equal(t, "ic| \u001B[36mstr\u001B[0m: \"hello\"\n", actual) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Longlone 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 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= 6 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 7 | github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a h1:ZHfoO7ZJhws9NU1kzZhStUnnVQiPtDe1PzpUnc6HirU= 8 | github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a/go.mod h1:DNrlr0AR9NsHD/aoc2pPeu4uSBZ/71yCHkR42yrzW3M= 9 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 10 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 13 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # IceCream-Go 2 | 3 | A Go port of Python's [IceCream](https://github.com/gruns/icecream). 4 | 5 | ## Usage 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | . "github.com/WAY29/icecream-go/icecream" 12 | ) 13 | 14 | func foo(a int) int { 15 | return a + 333 16 | } 17 | 18 | 19 | func bar() { 20 | Ic() 21 | } 22 | 23 | func main() { 24 | Ic(foo(123)) 25 | // Outputs: 26 | // ic| foo(123): 456 27 | 28 | Ic(1 + 5) 29 | // Outputs: 30 | // ic| 1 + 5: 6 31 | 32 | Ic(foo(123), 1 + 5) 33 | // Outputs: 34 | // ic| foo(123): 456, 1 + 5: 6 35 | bar() 36 | } 37 | // Outputs: 38 | // ic| main.go:12 in main.bar() 39 | ``` 40 | 41 | ## Depends 42 | - [reflectsource](https://github.com/shurcooL/go/tree/master/reflectsource) 43 | 44 | 45 | ## Installation 46 | 47 | ``` 48 | go get -u "github.com/WAY29/icecream-go/icecream" 49 | ``` 50 | 51 | ## Import 52 | 53 | ```go 54 | import ic "github.com/WAY29/icecream-go/icecream" 55 | // or just import . "github.com/WAY29/icecream-go/icecream" 56 | ``` 57 | 58 | ## Configuration 59 | 60 | If you want to change the prefix of the output, you can call `icecream.ConfigurePrefix("Hello| ")` (by default the prefix is `ic| `). 61 | 62 | If you want to change how the result is outputted, you can call `icecream.ConfigureOutputFunction(f)`. func may be type of `func(s interface{})`. 63 | For example, if you want to log your messages to a file: 64 | ```go 65 | func logfile(s string) { 66 | filePath := "log.log" 67 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666) 68 | if err != nil { 69 | fmt.Println(err) 70 | } 71 | defer file.Close() 72 | write := bufio.NewWriter(file) 73 | write.WriteString(s) 74 | write.Flush() 75 | } 76 | func main() { 77 | ic.ConfigureOutputFunction(logfile) 78 | ic.Ic(1, 2, 3) 79 | 80 | // output to stringBuilder 81 | builder := strings.Builder{} 82 | ic.ConfigureOutputFunction(&builder) 83 | ic.Ic("hello") 84 | } 85 | ``` 86 | 87 | If you want to change how the value is outputted, you can call `icecream.ConfigureArgToStringFunction(f)`, func may be type of `func(v interface{}) interface{}`. 88 | For example, if you want to print more detail about a string: 89 | ```go 90 | func toString(v interface{}) interface{} { 91 | rv := reflect.ValueOf(v) 92 | if rv.Kind() == reflect.String { 93 | return fmt.Sprintf("[!string %#v with length %d!]", v, len(v.(string))) 94 | } 95 | return fmt.Sprintf("%#v", v) 96 | } 97 | func main() { 98 | s := "string" 99 | ic.ConfigureArgToStringFunction(toString) 100 | ic.Ic(s) 101 | ic.Ic("test") 102 | } 103 | ``` 104 | 105 | If you want to change the value name is outputted, you can call `icecream.ConfigureArgNameFormatterFunc`. 106 | For example, if you want to print value name with ansi color: 107 | ```go 108 | func main() { 109 | s := "string" 110 | ic.ConfigureArgNameFormatterFunc(func(name string) string { 111 | return "\u001B[36m" + name + "\u001B[0m" 112 | }) 113 | ic.Ic(s) 114 | ic.Ic("test") 115 | } 116 | ``` 117 | 118 | If you want to add call's filename, line number, and parent function to ic's output, you can call `icecream.ConfigureIncludeContext(true)`. 119 | ```go 120 | func main() { 121 | ic.ConfigureIncludeContext(true) 122 | ic.Ic(1, "asd") 123 | } 124 | ``` 125 | 126 | If you want to reset configuration, you can call `icecream.ResetPrefix()`,`icecream.ResetOutputFunction()`, `icecream.ResetArgToStringFunction()`,`icecream.ResetIncludeContext()` . 127 | 128 | ## Return Value 129 | `Ic()` returns its arguments, so `Ic()` can easily be inserted into pre-existing code. 130 | 131 | ```go 132 | func half(i interface{}) int { 133 | if ii, ok := i.(int); ok { 134 | return ii / 2 135 | } 136 | 137 | return -1 138 | } 139 | 140 | 141 | func main() { 142 | a := 6 143 | b := half(ic.Ic(a)[0]) 144 | ic.Ic(b) 145 | } 146 | ``` 147 | Prints 148 | ``` 149 | ic| a: 6 150 | ic| b: 3 151 | ``` 152 | 153 | ## Miscellaneous 154 | 155 | `Format(...interface{})` is like `ic()` but the output is returned as a string instead of written to stderr. 156 | 157 | ```go 158 | func main() { 159 | result := ic.Format("sup") 160 | fmt.Printf("%s", result) 161 | } 162 | ``` 163 | 164 | Additionally, `Ic()`'s output can be entirely disabled, and later re-enabled, with `Disable()` and `Enable()` respectively. 165 | ```go 166 | func main() { 167 | ic.Ic(1) 168 | ic.Disable() 169 | ic.Ic(2) 170 | ic.Enable() 171 | ic.Ic(3) 172 | } 173 | ``` 174 | Prints 175 | ``` 176 | ic| 1 177 | ic| 3 178 | ``` -------------------------------------------------------------------------------- /icecream/icecream.go: -------------------------------------------------------------------------------- 1 | package icecream 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "reflect" 9 | "runtime" 10 | 11 | "github.com/shurcooL/go/reflectsource" 12 | ) 13 | 14 | var prefixString = "ic| " 15 | var outputFunction = reflect.ValueOf(os.Stderr.WriteString) 16 | var argToStringFunction reflect.Value 17 | var argNameFormatterFunc reflect.Value 18 | var includeContext = false 19 | var disableOutput = false 20 | 21 | func printMsg(msg interface{}) { 22 | rf := outputFunction 23 | s := reflect.ValueOf(msg) 24 | if rf.Kind() == reflect.Func && !disableOutput { 25 | rf.Call([]reflect.Value{s}) 26 | } 27 | } 28 | 29 | func formatValue(v interface{}) interface{} { 30 | raf := argToStringFunction 31 | rafk := raf.Kind() 32 | if rafk == reflect.Invalid || rafk == reflect.Bool { // ? argToStringFunction default is fmt.Sprintf 33 | return fmt.Sprintf("%#v", v) 34 | } else if rafk == reflect.Func { // ? call custom argToStringFunction 35 | results := raf.Call([]reflect.Value{reflect.ValueOf(v)}) 36 | return results[0].Interface() 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func formatArgName(argName string) string { 43 | raf := argNameFormatterFunc 44 | if raf.Kind() == reflect.Func { // call custom argNameFormatterFunc 45 | results := raf.Call([]reflect.Value{reflect.ValueOf(argName)}) 46 | if ret, ok := results[0].Interface().(string); ok { 47 | return ret 48 | } 49 | } 50 | 51 | return argName 52 | } 53 | 54 | func Ic(values ...interface{}) []interface{} { 55 | var ( 56 | msg string 57 | returnValue = make([]interface{}, 0, len(values)) 58 | ) 59 | 60 | line := 0 61 | pc, filename, line, ok := runtime.Caller(1) 62 | funcName := runtime.FuncForPC(pc).Name() 63 | pwd, _ := os.Getwd() 64 | relFilename, _ := filepath.Rel(pwd, filename) 65 | if ok { 66 | lenOfValues := len(values) 67 | if lenOfValues > 0 { // ? print value 68 | if includeContext { // ? print prefix 69 | msg = fmt.Sprintf("%s%s:%d in %s()- ", prefixString, relFilename, line, funcName) 70 | } else { 71 | msg = prefixString 72 | } 73 | printMsg(msg) 74 | for i, v := range values { // ? print value 75 | vName := reflectsource.GetParentArgExprAsString(uint32(i)) 76 | fmtV := fmt.Sprintf("%#v", v) 77 | if vName == fmtV { // ? vName the same as value, just print one 78 | printMsg(formatValue(v)) 79 | } else { 80 | msg = fmt.Sprintf("%s: ", formatArgName(vName)) 81 | printMsg(msg) 82 | printMsg(formatValue(v)) 83 | } 84 | if i < lenOfValues-1 { // ? print split character 85 | printMsg(", ") 86 | } 87 | 88 | returnValue = append(returnValue, v) // ? add value in returnValue 89 | } 90 | printMsg("\n") 91 | } else { // ? print line if value is nil 92 | msg = fmt.Sprintf("%s%s:%d in %s()\n", prefixString, relFilename, line, funcName) 93 | printMsg(msg) 94 | } 95 | } 96 | 97 | return returnValue 98 | } 99 | 100 | func Format(values ...interface{}) string { 101 | var ( 102 | returnMsg string = "" 103 | ) 104 | 105 | line := 0 106 | pc, filename, line, ok := runtime.Caller(1) 107 | funcName := runtime.FuncForPC(pc).Name() 108 | pwd, _ := os.Getwd() 109 | relFilename, _ := filepath.Rel(pwd, filename) 110 | if ok { 111 | lenOfValues := len(values) 112 | if lenOfValues > 0 { // ? print value 113 | if includeContext { // ? print prefix 114 | returnMsg += fmt.Sprintf("%s%s:%d in %s()- ", prefixString, relFilename, line, funcName) 115 | } else { 116 | returnMsg += prefixString 117 | } 118 | for i, v := range values { // ? print value 119 | vName := reflectsource.GetParentArgExprAsString(uint32(i)) 120 | fmtV := fmt.Sprintf("%#v", v) 121 | if vName == fmtV { // ? vName the same as value, just print one 122 | returnMsg += fmt.Sprintf("%v", formatValue(v)) 123 | } else { 124 | returnMsg += fmt.Sprintf("%s: ", vName) 125 | returnMsg += fmt.Sprintf("%v", formatValue(v)) 126 | } 127 | if i < lenOfValues-1 { // ? print split character 128 | returnMsg += ", " 129 | } 130 | 131 | } 132 | } else { // ? print line if value is nil 133 | returnMsg = fmt.Sprintf("%s%s:%d in %s()\n", prefixString, relFilename, line, funcName) 134 | } 135 | } 136 | 137 | return returnMsg 138 | } 139 | 140 | func ConfigurePrefix(prefix string) { 141 | prefixString = prefix 142 | } 143 | 144 | // ConfigureOutputFunction modifies output that writes the final msg into w. 145 | // w should be io.StringWriter or function with string arg, 146 | // the return value indicates whether w is legal 147 | func ConfigureOutputFunction(w interface{}) bool { 148 | if strWriter, ok := w.(io.StringWriter); ok { 149 | outputFunction = reflect.ValueOf(strWriter.WriteString) 150 | return true 151 | } 152 | rf := reflect.ValueOf(w) 153 | if rf.Kind() == reflect.Func { 154 | outputFunction = rf 155 | return true 156 | } 157 | return false 158 | } 159 | 160 | func ConfigureArgToStringFunction(f interface{}) bool { 161 | rf := reflect.ValueOf(f) 162 | if rf.Kind() == reflect.Func { 163 | argToStringFunction = rf 164 | return true 165 | } 166 | return false 167 | } 168 | 169 | // ConfigureArgNameFormatterFunc config arg name formatter. 170 | // f should be `func(string) string`, 171 | // the return value indicates whether w is legal. 172 | func ConfigureArgNameFormatterFunc(f func(string) string) bool { 173 | rf := reflect.ValueOf(f) 174 | if rf.Kind() == reflect.Func { 175 | argNameFormatterFunc = rf 176 | return true 177 | } 178 | return false 179 | } 180 | 181 | func Disable() { 182 | disableOutput = true 183 | } 184 | 185 | func ConfigureIncludeContext(boolean bool) { 186 | includeContext = boolean 187 | } 188 | 189 | func ResetPrefix() { 190 | prefixString = "ic| " 191 | } 192 | 193 | func ResetOutputFunction() { 194 | outputFunction = reflect.ValueOf(os.Stderr.WriteString) 195 | } 196 | 197 | func ResetArgToStringFunction() { 198 | argToStringFunction = reflect.ValueOf(false) 199 | } 200 | 201 | func ResetIncludeContext() { 202 | includeContext = false 203 | } 204 | 205 | func Enable() { 206 | disableOutput = false 207 | } 208 | --------------------------------------------------------------------------------