├── .gitignore ├── vendor ├── github.com │ ├── kr │ │ ├── pretty │ │ │ ├── .gitignore │ │ │ ├── Readme │ │ │ ├── License │ │ │ ├── zero.go │ │ │ ├── pretty.go │ │ │ ├── diff.go │ │ │ └── formatter.go │ │ └── text │ │ │ ├── doc.go │ │ │ ├── Readme │ │ │ ├── License │ │ │ ├── indent.go │ │ │ └── wrap.go │ └── rogpeppe │ │ └── go-internal │ │ ├── fmtsort │ │ ├── mapelem.go │ │ └── sort.go │ │ └── LICENSE └── modules.txt ├── qq.sublime-snippet ├── .vscode ├── extensions.json └── settings.json ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── pr.yml ├── go.mod ├── testdata └── sample1.go ├── CONTRIBUTING.md ├── doc.go ├── go.sum ├── LICENSE ├── .golangci.yml ├── .devcontainer └── devcontainer.json ├── q.go ├── logger_test.go ├── README.md ├── logger.go ├── args.go └── args_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/.gitignore: -------------------------------------------------------------------------------- 1 | [568].out 2 | _go* 3 | _test* 4 | _obj 5 | /.idea 6 | -------------------------------------------------------------------------------- /vendor/github.com/kr/text/doc.go: -------------------------------------------------------------------------------- 1 | // Package text provides rudimentary functions for manipulating text in 2 | // paragraphs. 3 | package text 4 | -------------------------------------------------------------------------------- /qq.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | qq 6 | source.go 7 | 8 | -------------------------------------------------------------------------------- /vendor/github.com/kr/text/Readme: -------------------------------------------------------------------------------- 1 | This is a Go package for manipulating paragraphs of text. 2 | 3 | See http://go.pkgdoc.org/github.com/kr/text for full documentation. 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "davidanson.vscode-markdownlint", 4 | "golang.go", 5 | "redhat.vscode-yaml" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ryboe/q 2 | 3 | go 1.25.0 4 | 5 | require github.com/kr/pretty v0.3.1 6 | 7 | require ( 8 | github.com/kr/text v0.2.0 // indirect 9 | github.com/rogpeppe/go-internal v1.14.1 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/Readme: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import "github.com/kr/pretty" 4 | 5 | Package pretty provides pretty-printing for Go values. 6 | 7 | Documentation 8 | 9 | http://godoc.org/github.com/kr/pretty 10 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/kr/pretty v0.3.1 2 | ## explicit; go 1.12 3 | github.com/kr/pretty 4 | # github.com/kr/text v0.2.0 5 | ## explicit 6 | github.com/kr/text 7 | # github.com/rogpeppe/go-internal v1.14.1 8 | ## explicit; go 1.23 9 | github.com/rogpeppe/go-internal/fmtsort 10 | -------------------------------------------------------------------------------- /testdata/sample1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "q" 4 | 5 | func main() { 6 | a := 123 7 | b := "hello world" 8 | c := 3.1415926 9 | d := func(n int) bool { return n > 0 }(1) 10 | e := []int{1, 2, 3} 11 | f := []byte("goodbye world") 12 | g := e[1:] 13 | 14 | q.Q(a, b, c, d, e, f, g) 15 | } 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Before writing any code you should know that I consider `q` to be feature complete. I will accept 4 | bug fixes and obvious code cleanups, but new features are likely to get rejected. `q` is intended to 5 | be a stupid simple tool with no knobs. 6 | 7 | ## Create an Issue First 8 | 9 | Your PR is more likely to be accepted if you open an issue first. 10 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package q provides quick and dirty debugging output for tired programmers. 3 | 4 | q.Q() is a fast way to pretty-print variables. It's easier than typing 5 | fmt.Printf("%#v", whatever). The output will be colorized and nicely formatted. 6 | The output goes to $TMPDIR/q, away from the noise of stdout. 7 | 8 | q exports a single Q() function. This is how you use it: 9 | 10 | import "q" 11 | ... 12 | q.Q(a, b, c) 13 | */ 14 | package q 15 | -------------------------------------------------------------------------------- /vendor/github.com/rogpeppe/go-internal/fmtsort/mapelem.go: -------------------------------------------------------------------------------- 1 | package fmtsort 2 | 3 | import "reflect" 4 | 5 | const brokenNaNs = false 6 | 7 | func mapElems(mapValue reflect.Value) ([]reflect.Value, []reflect.Value) { 8 | // Note: this code is arranged to not panic even in the presence 9 | // of a concurrent map update. The runtime is responsible for 10 | // yelling loudly if that happens. See issue 33275. 11 | n := mapValue.Len() 12 | key := make([]reflect.Value, 0, n) 13 | value := make([]reflect.Value, 0, n) 14 | iter := mapValue.MapRange() 15 | for iter.Next() { 16 | key = append(key, iter.Key()) 17 | value = append(value, iter.Value()) 18 | } 19 | return key, value 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | schedule: 7 | - cron: 37 11 * * 2 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | security-events: write 16 | 17 | steps: 18 | - name: checkout repository 19 | uses: actions/checkout@v5 20 | with: 21 | fetch-depth: 1 22 | 23 | # Initializes the CodeQL tools for scanning. 24 | - name: initialize codeql 25 | uses: github/codeql-action/init@v3 26 | with: 27 | languages: go 28 | build-mode: autobuild 29 | 30 | - name: perform codeql analysis 31 | uses: github/codeql-action/analyze@v3 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 3 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 4 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 5 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 6 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 7 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 8 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 9 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "files.trimTrailingWhitespace": true, 6 | "go.diagnostic.vulncheck": "Imports", // check versions in go.mod for vulns. complements gopls.ui.codelenses.vulncheck 7 | "go.lintTool": "golangci-lint-v2", // Go: Install/Update Tools installs v1 by default 8 | "go.survey.prompt": false, 9 | "go.toolsManagement.autoUpdate": true, 10 | "gopls": { 11 | "formatting.gofumpt": true, // set this instead of go.formatTool 12 | "ui.codelenses": { 13 | // run govulncheck to check Go code for use of vulnerable funcs. complements go.diagnostic.vulncheck 14 | "vulncheck": true 15 | }, 16 | "ui.completion.usePlaceholders": true, // completion text will have placeholders that you can tab through 17 | "ui.diagnostic.staticcheck": true, // enable all staticcheck checks 18 | "ui.semanticTokens": true // more accurate syntax highlighting 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/License: -------------------------------------------------------------------------------- 1 | Copyright 2012 Keith Rarick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/kr/text/License: -------------------------------------------------------------------------------- 1 | Copyright 2012 Keith Rarick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan Boehning, Jaime Piña, Tony Rizko 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 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/zero.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func nonzero(v reflect.Value) bool { 8 | switch v.Kind() { 9 | case reflect.Bool: 10 | return v.Bool() 11 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 12 | return v.Int() != 0 13 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 14 | return v.Uint() != 0 15 | case reflect.Float32, reflect.Float64: 16 | return v.Float() != 0 17 | case reflect.Complex64, reflect.Complex128: 18 | return v.Complex() != complex(0, 0) 19 | case reflect.String: 20 | return v.String() != "" 21 | case reflect.Struct: 22 | for i := 0; i < v.NumField(); i++ { 23 | if nonzero(getField(v, i)) { 24 | return true 25 | } 26 | } 27 | return false 28 | case reflect.Array: 29 | for i := 0; i < v.Len(); i++ { 30 | if nonzero(v.Index(i)) { 31 | return true 32 | } 33 | } 34 | return false 35 | case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func: 36 | return !v.IsNil() 37 | case reflect.UnsafePointer: 38 | return v.Pointer() != 0 39 | } 40 | return true 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | lint: 9 | name: golangci-lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: checkout repository 13 | uses: actions/checkout@v5 14 | with: 15 | fetch-depth: 1 16 | - name: set up go 17 | uses: WillAbides/setup-go-faster@v1 18 | with: 19 | go-version-file: go.mod 20 | - name: lint 21 | uses: golangci/golangci-lint-action@v8 22 | with: 23 | version: latest 24 | 25 | check: 26 | name: go vet and staticcheck 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: checkout repository 30 | uses: actions/checkout@v5 31 | with: 32 | fetch-depth: 1 33 | - name: set up go 34 | uses: WillAbides/setup-go-faster@v1 35 | with: 36 | go-version-file: go.mod 37 | - name: run go vet 38 | run: go vet ./... 39 | - name: run staticcheck 40 | uses: dominikh/staticcheck-action@v1 41 | with: 42 | version: latest 43 | 44 | tests: 45 | name: unit tests with race detector enabled 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: checkout repository 49 | uses: actions/checkout@v5 50 | with: 51 | fetch-depth: 1 52 | - name: set up go 53 | uses: WillAbides/setup-go-faster@v1 54 | with: 55 | go-version-file: go.mod 56 | - name: run unit tests 57 | run: go test -v -race ./... 58 | -------------------------------------------------------------------------------- /vendor/github.com/rogpeppe/go-internal/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | run: 4 | modules-download-mode: vendor 5 | tests: true 6 | 7 | linters: 8 | default: all 9 | disable: 10 | - depguard # enforces that only deps on a whitelist can be used (meant for orgs, not small projects) 11 | - exhaustruct 12 | - forbidigo # we need to use fmt.Print*() 13 | - gosmopolitan # we use chinese characters in a test 14 | - govet # we run the official go vet separately 15 | - noinlineerr # inline errors are fine 16 | - nolintlint # we do occasionally need to suppress linter false positives 17 | - nonamedreturns # named returns often help readability 18 | - paralleltest # tests only take 2.5s to run. no need to parallelize 19 | - staticcheck # dominickh doesn't recommend running staticcheck as part of golangci-lint, so we run Real Staticcheck™ separately 20 | - testpackage 21 | - varnamelen # makes bad suggestions 22 | - wsl 23 | - wsl_v5 24 | settings: 25 | gocritic: 26 | disabled-checks: 27 | - whyNoLint 28 | enabled-tags: 29 | - diagnostic 30 | - opinionated 31 | - performance 32 | - style 33 | gocyclo: 34 | min-complexity: 10 35 | exclusions: 36 | generated: lax 37 | presets: 38 | - comments 39 | - common-false-positives 40 | - std-error-handling 41 | paths: 42 | - third_party$ 43 | - builtin$ 44 | - examples$ 45 | 46 | formatters: 47 | enable: 48 | - gofumpt 49 | settings: 50 | gofumpt: 51 | extra-rules: true 52 | exclusions: 53 | generated: lax 54 | paths: 55 | - third_party$ 56 | - builtin$ 57 | - examples$ 58 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Q", 3 | "image": "ghcr.io/ryboe/gocodespace:latest", 4 | // dlv needs these capabilities. It needs to run the ptrace (process trace) 5 | // syscall, and we need to disable the default seccomp profile applied to 6 | // docker containers. 7 | // https://github.com/go-delve/delve/blob/master/Documentation/faq.md#how-do-i-use-delve-with-docker 8 | "runArgs": [ 9 | "--cap-add=SYS_PTRACE", 10 | "--security-opt", 11 | "seccomp=unconfined" 12 | ], 13 | "customizations": { 14 | "vscode": { 15 | "settings": { 16 | "editor.formatOnSave": true, 17 | "extensions.verifySignature": false, // remove when bug is fixed: https://github.com/microsoft/vscode/issues/174632 18 | "files.insertFinalNewline": true, 19 | "files.trimFinalNewlines": true, 20 | "files.trimTrailingWhitespace": true, 21 | "go.diagnostic.vulncheck": "Imports", // check versions in go.mod for vulns. complements gopls.ui.codelenses.vulncheck 22 | "go.lintTool": "golangci-lint-v2", // Go: Install/Update Tools installs v1 by default 23 | "go.survey.prompt": false, 24 | "go.toolsManagement.autoUpdate": true, 25 | "gopls": { 26 | "formatting.gofumpt": true, // set this instead of go.formatTool 27 | "ui.codelenses": { 28 | // run govulncheck to check Go code for use of vulnerable funcs. complements go.diagnostic.vulncheck 29 | "vulncheck": true 30 | }, 31 | "ui.completion.usePlaceholders": true, // completion text will have placeholders that you can tab through 32 | "ui.diagnostic.staticcheck": true, // enable all staticcheck checks 33 | "ui.semanticTokens": true // more accurate syntax highlighting 34 | } 35 | }, 36 | "extensions": [ 37 | "davidanson.vscode-markdownlint", 38 | "golang.go", 39 | "redhat.vscode-yaml" 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /vendor/github.com/kr/text/indent.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // Indent inserts prefix at the beginning of each non-empty line of s. The 8 | // end-of-line marker is NL. 9 | func Indent(s, prefix string) string { 10 | return string(IndentBytes([]byte(s), []byte(prefix))) 11 | } 12 | 13 | // IndentBytes inserts prefix at the beginning of each non-empty line of b. 14 | // The end-of-line marker is NL. 15 | func IndentBytes(b, prefix []byte) []byte { 16 | var res []byte 17 | bol := true 18 | for _, c := range b { 19 | if bol && c != '\n' { 20 | res = append(res, prefix...) 21 | } 22 | res = append(res, c) 23 | bol = c == '\n' 24 | } 25 | return res 26 | } 27 | 28 | // Writer indents each line of its input. 29 | type indentWriter struct { 30 | w io.Writer 31 | bol bool 32 | pre [][]byte 33 | sel int 34 | off int 35 | } 36 | 37 | // NewIndentWriter makes a new write filter that indents the input 38 | // lines. Each line is prefixed in order with the corresponding 39 | // element of pre. If there are more lines than elements, the last 40 | // element of pre is repeated for each subsequent line. 41 | func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer { 42 | return &indentWriter{ 43 | w: w, 44 | pre: pre, 45 | bol: true, 46 | } 47 | } 48 | 49 | // The only errors returned are from the underlying indentWriter. 50 | func (w *indentWriter) Write(p []byte) (n int, err error) { 51 | for _, c := range p { 52 | if w.bol { 53 | var i int 54 | i, err = w.w.Write(w.pre[w.sel][w.off:]) 55 | w.off += i 56 | if err != nil { 57 | return n, err 58 | } 59 | } 60 | _, err = w.w.Write([]byte{c}) 61 | if err != nil { 62 | return n, err 63 | } 64 | n++ 65 | w.bol = c == '\n' 66 | if w.bol { 67 | w.off = 0 68 | if w.sel < len(w.pre)-1 { 69 | w.sel++ 70 | } 71 | } 72 | } 73 | return n, nil 74 | } 75 | -------------------------------------------------------------------------------- /q.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Ryan Boehning. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package q 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // nolint: gochecknoglobals 12 | var ( 13 | // std is the singleton logger. 14 | std logger 15 | 16 | // CallDepth allows setting the number of levels runtime.Caller will 17 | // skip when looking up the caller of the q.Q function. This allows 18 | // the `q` package to be wrapped by a project-specific wrapping function, 19 | // which would increase the depth by at least one. It's better to not 20 | // include calls to `q.Q` in released code at all and scrub them before, 21 | // a build is created, but in some cases it might be useful to provide 22 | // builds that do include the additional debug output provided by `q.Q`. 23 | // This also allows the consumer of the package to control what happens 24 | // with leftover `q.Q` calls. Defaults to 2, because the user code calls 25 | // q.Q(), which calls getCallerInfo(). 26 | CallDepth = 2 27 | ) 28 | 29 | // Q pretty-prints the given arguments to the $TMPDIR/q log file. 30 | func Q(v ...interface{}) { 31 | std.mu.Lock() 32 | defer std.mu.Unlock() 33 | 34 | // Flush the buffered writes to disk. 35 | defer func() { 36 | if err := std.flush(); err != nil { 37 | fmt.Println(err) 38 | } 39 | }() 40 | 41 | args := formatArgs(v...) 42 | funcName, file, line, err := getCallerInfo() 43 | if err != nil { 44 | std.output(args...) // no name=value printing 45 | 46 | return 47 | } 48 | 49 | // Print a header line if this q.Q() call is in a different file or 50 | // function than the previous q.Q() call, or if the 2s timer expired. 51 | // A header line looks like this: [14:00:36 main.go main.main:122]. 52 | header := std.header(funcName, file, line) 53 | if header != "" { 54 | fmt.Fprint(&std.buf, "\n", header, "\n") 55 | } 56 | 57 | // q.Q(foo, bar, baz) -> []string{"foo", "bar", "baz"} 58 | names, err := argNames(file, line) 59 | if err != nil { 60 | std.output(args...) // no name=value printing 61 | 62 | return 63 | } 64 | 65 | // Convert the arguments to name=value strings. 66 | args = prependArgName(names, args) 67 | std.output(args...) 68 | } 69 | -------------------------------------------------------------------------------- /vendor/github.com/kr/text/wrap.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "bytes" 5 | "math" 6 | ) 7 | 8 | var ( 9 | nl = []byte{'\n'} 10 | sp = []byte{' '} 11 | ) 12 | 13 | const defaultPenalty = 1e5 14 | 15 | // Wrap wraps s into a paragraph of lines of length lim, with minimal 16 | // raggedness. 17 | func Wrap(s string, lim int) string { 18 | return string(WrapBytes([]byte(s), lim)) 19 | } 20 | 21 | // WrapBytes wraps b into a paragraph of lines of length lim, with minimal 22 | // raggedness. 23 | func WrapBytes(b []byte, lim int) []byte { 24 | words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp) 25 | var lines [][]byte 26 | for _, line := range WrapWords(words, 1, lim, defaultPenalty) { 27 | lines = append(lines, bytes.Join(line, sp)) 28 | } 29 | return bytes.Join(lines, nl) 30 | } 31 | 32 | // WrapWords is the low-level line-breaking algorithm, useful if you need more 33 | // control over the details of the text wrapping process. For most uses, either 34 | // Wrap or WrapBytes will be sufficient and more convenient. 35 | // 36 | // WrapWords splits a list of words into lines with minimal "raggedness", 37 | // treating each byte as one unit, accounting for spc units between adjacent 38 | // words on each line, and attempting to limit lines to lim units. Raggedness 39 | // is the total error over all lines, where error is the square of the 40 | // difference of the length of the line and lim. Too-long lines (which only 41 | // happen when a single word is longer than lim units) have pen penalty units 42 | // added to the error. 43 | func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte { 44 | n := len(words) 45 | 46 | length := make([][]int, n) 47 | for i := 0; i < n; i++ { 48 | length[i] = make([]int, n) 49 | length[i][i] = len(words[i]) 50 | for j := i + 1; j < n; j++ { 51 | length[i][j] = length[i][j-1] + spc + len(words[j]) 52 | } 53 | } 54 | 55 | nbrk := make([]int, n) 56 | cost := make([]int, n) 57 | for i := range cost { 58 | cost[i] = math.MaxInt32 59 | } 60 | for i := n - 1; i >= 0; i-- { 61 | if length[i][n-1] <= lim || i == n-1 { 62 | cost[i] = 0 63 | nbrk[i] = n 64 | } else { 65 | for j := i + 1; j < n; j++ { 66 | d := lim - length[i][j-1] 67 | c := d*d + cost[j] 68 | if length[i][j-1] > lim { 69 | c += pen // too-long lines get a worse penalty 70 | } 71 | if c < cost[i] { 72 | cost[i] = c 73 | nbrk[i] = j 74 | } 75 | } 76 | } 77 | } 78 | 79 | var lines [][][]byte 80 | i := 0 81 | for i < n { 82 | lines = append(lines, words[i:nbrk[i]]) 83 | i = nbrk[i] 84 | } 85 | return lines 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/pretty.go: -------------------------------------------------------------------------------- 1 | // Package pretty provides pretty-printing for Go values. This is 2 | // useful during debugging, to avoid wrapping long output lines in 3 | // the terminal. 4 | // 5 | // It provides a function, Formatter, that can be used with any 6 | // function that accepts a format string. It also provides 7 | // convenience wrappers for functions in packages fmt and log. 8 | package pretty 9 | 10 | import ( 11 | "fmt" 12 | "io" 13 | "log" 14 | "reflect" 15 | ) 16 | 17 | // Errorf is a convenience wrapper for fmt.Errorf. 18 | // 19 | // Calling Errorf(f, x, y) is equivalent to 20 | // fmt.Errorf(f, Formatter(x), Formatter(y)). 21 | func Errorf(format string, a ...interface{}) error { 22 | return fmt.Errorf(format, wrap(a, false)...) 23 | } 24 | 25 | // Fprintf is a convenience wrapper for fmt.Fprintf. 26 | // 27 | // Calling Fprintf(w, f, x, y) is equivalent to 28 | // fmt.Fprintf(w, f, Formatter(x), Formatter(y)). 29 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) { 30 | return fmt.Fprintf(w, format, wrap(a, false)...) 31 | } 32 | 33 | // Log is a convenience wrapper for log.Printf. 34 | // 35 | // Calling Log(x, y) is equivalent to 36 | // log.Print(Formatter(x), Formatter(y)), but each operand is 37 | // formatted with "%# v". 38 | func Log(a ...interface{}) { 39 | log.Print(wrap(a, true)...) 40 | } 41 | 42 | // Logf is a convenience wrapper for log.Printf. 43 | // 44 | // Calling Logf(f, x, y) is equivalent to 45 | // log.Printf(f, Formatter(x), Formatter(y)). 46 | func Logf(format string, a ...interface{}) { 47 | log.Printf(format, wrap(a, false)...) 48 | } 49 | 50 | // Logln is a convenience wrapper for log.Printf. 51 | // 52 | // Calling Logln(x, y) is equivalent to 53 | // log.Println(Formatter(x), Formatter(y)), but each operand is 54 | // formatted with "%# v". 55 | func Logln(a ...interface{}) { 56 | log.Println(wrap(a, true)...) 57 | } 58 | 59 | // Print pretty-prints its operands and writes to standard output. 60 | // 61 | // Calling Print(x, y) is equivalent to 62 | // fmt.Print(Formatter(x), Formatter(y)), but each operand is 63 | // formatted with "%# v". 64 | func Print(a ...interface{}) (n int, errno error) { 65 | return fmt.Print(wrap(a, true)...) 66 | } 67 | 68 | // Printf is a convenience wrapper for fmt.Printf. 69 | // 70 | // Calling Printf(f, x, y) is equivalent to 71 | // fmt.Printf(f, Formatter(x), Formatter(y)). 72 | func Printf(format string, a ...interface{}) (n int, errno error) { 73 | return fmt.Printf(format, wrap(a, false)...) 74 | } 75 | 76 | // Println pretty-prints its operands and writes to standard output. 77 | // 78 | // Calling Println(x, y) is equivalent to 79 | // fmt.Println(Formatter(x), Formatter(y)), but each operand is 80 | // formatted with "%# v". 81 | func Println(a ...interface{}) (n int, errno error) { 82 | return fmt.Println(wrap(a, true)...) 83 | } 84 | 85 | // Sprint is a convenience wrapper for fmt.Sprintf. 86 | // 87 | // Calling Sprint(x, y) is equivalent to 88 | // fmt.Sprint(Formatter(x), Formatter(y)), but each operand is 89 | // formatted with "%# v". 90 | func Sprint(a ...interface{}) string { 91 | return fmt.Sprint(wrap(a, true)...) 92 | } 93 | 94 | // Sprintf is a convenience wrapper for fmt.Sprintf. 95 | // 96 | // Calling Sprintf(f, x, y) is equivalent to 97 | // fmt.Sprintf(f, Formatter(x), Formatter(y)). 98 | func Sprintf(format string, a ...interface{}) string { 99 | return fmt.Sprintf(format, wrap(a, false)...) 100 | } 101 | 102 | func wrap(a []interface{}, force bool) []interface{} { 103 | w := make([]interface{}, len(a)) 104 | for i, x := range a { 105 | w[i] = formatter{v: reflect.ValueOf(x), force: force} 106 | } 107 | return w 108 | } 109 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Ryan Boehning. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package q 6 | 7 | import ( 8 | "fmt" 9 | "strconv" 10 | "strings" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | // TestHeader verifies that logger.header() returns a header line with the 16 | // expected filename, function name, and line number. 17 | // nolint: funlen 18 | func TestHeader(t *testing.T) { 19 | testCases := []struct { 20 | lastFile, lastFunc string 21 | currFile, currFunc string 22 | timerExpired bool 23 | wantEmptyString bool 24 | }{ 25 | { 26 | lastFile: "foo.go", 27 | lastFunc: "foo.Bar", 28 | currFile: "foo.go", 29 | currFunc: "foo.Bar", 30 | timerExpired: false, 31 | wantEmptyString: true, 32 | }, 33 | { 34 | lastFile: "hello.go", 35 | lastFunc: "main.Greeting", 36 | currFile: "hello.go", 37 | currFunc: "main.Farewell", 38 | timerExpired: false, 39 | wantEmptyString: false, 40 | }, 41 | { 42 | lastFile: "hello.go", 43 | lastFunc: "main.Greeting", 44 | currFile: "goodbye.go", 45 | currFunc: "main.Greeting", 46 | timerExpired: false, 47 | wantEmptyString: false, 48 | }, 49 | { 50 | lastFile: "hello.go", 51 | lastFunc: "main.Greeting", 52 | currFile: "goodbye.go", 53 | currFunc: "main.Farewell", 54 | timerExpired: false, 55 | wantEmptyString: false, 56 | }, 57 | { 58 | lastFile: "goodbye.go", 59 | lastFunc: "main.Goodbye", 60 | currFile: "goodbye.go", 61 | currFunc: "main.Goodbye", 62 | timerExpired: false, 63 | wantEmptyString: true, 64 | }, 65 | { 66 | lastFile: "goodbye.go", 67 | lastFunc: "main.Goodbye", 68 | currFile: "goodbye.go", 69 | currFunc: "main.Goodbye", 70 | timerExpired: true, 71 | wantEmptyString: false, 72 | }, 73 | } 74 | 75 | for _, tc := range testCases { 76 | l := logger{ 77 | lastFile: tc.lastFile, 78 | lastFunc: tc.lastFunc, 79 | } 80 | if !tc.timerExpired { 81 | l.lastWrite = time.Now() 82 | } 83 | 84 | const line = 123 85 | h := l.header(tc.currFunc, tc.currFile, line) 86 | if tc.wantEmptyString { 87 | if h == "" { 88 | continue 89 | } 90 | 91 | t.Fatalf("\nl.header(%s, %s, %d)\ngot: %q\nwant: %q", tc.currFunc, tc.lastFile, line, h, "") 92 | } 93 | 94 | if !strings.Contains(h, tc.currFunc) { 95 | t.Fatalf("\nl.header(%s, %s, %d)\ngot: %q\nmissing current function name", tc.currFunc, tc.currFile, line, h) 96 | } 97 | 98 | if !strings.Contains(h, tc.currFile) { 99 | t.Fatalf("\nl.header(%s, %s, %d)\ngot: %q\nmissing current file name", tc.currFunc, tc.currFile, line, h) 100 | } 101 | 102 | if !strings.Contains(h, strconv.Itoa(line)) { 103 | t.Fatalf("\nl.header(%s, %s, %d)\ngot: %q\nmissing line number", tc.currFunc, tc.currFile, line, h) 104 | } 105 | } 106 | } 107 | 108 | // TestOutput verifies that logger.output() prints the expected output to the 109 | // log buffer. 110 | func TestOutput(t *testing.T) { 111 | testCases := []struct { 112 | args []string 113 | want string 114 | }{ 115 | { 116 | args: []string{fmt.Sprintf("%s=%s", colorize("a", bold), colorize("int(1)", cyan))}, 117 | want: fmt.Sprintf("%s %s=%s\n", colorize("0.000s", yellow), colorize("a", bold), colorize("int(1)", cyan)), 118 | }, 119 | } 120 | 121 | for _, tc := range testCases { 122 | l := logger{start: time.Now().UTC()} 123 | l.output(tc.args...) 124 | 125 | got := l.buf.String() 126 | if got != tc.want { 127 | argString := strings.Join(tc.args, ", ") 128 | t.Fatalf("\nlogger.output(%s)\ngot: %swant: %s", argString, got, tc.want) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # q 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/ryboe/q)](https://goreportcard.com/report/github.com/ryboe/q) 4 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/ryboe/q)](https://pkg.go.dev/github.com/ryboe/q) 5 | 6 | q is a better way to do print statement debugging. 7 | 8 | Type `q.Q` instead of `fmt.Printf` and your variables will be printed like this: 9 | 10 | ![q output examples](https://i.imgur.com/OFmm7pb.png) 11 | 12 | ## Why is this better than `fmt.Printf`? 13 | 14 | * Faster to type 15 | * Pretty-printed vars and expressions 16 | * Easier to see inside structs 17 | * Doesn't go to noisy-ass stdout. It goes to `$TMPDIR/q`. 18 | * Pretty colors! 19 | 20 | ## Basic Usage 21 | 22 | ```go 23 | import "q" 24 | ... 25 | q.Q(a, b, c) 26 | ``` 27 | 28 | For best results, dedicate a terminal to tailing `$TMPDIR/q` while you work. 29 | 30 | ## Install 31 | 32 | ```sh 33 | git clone https://github.com/ryboe/q "$(go env GOPATH)"/src/q 34 | ``` 35 | 36 | Put these functions in your shell config. Typing `qq` or `rmqq` will then start 37 | tailing `$TMPDIR/q`. 38 | 39 | ```sh 40 | qq() { 41 | clear 42 | 43 | logpath="$TMPDIR/q" 44 | if [[ -z "$TMPDIR" ]]; then 45 | logpath="/tmp/q" 46 | fi 47 | 48 | if [[ ! -f "$logpath" ]]; then 49 | echo 'Q LOG' > "$logpath" 50 | fi 51 | 52 | tail -100f -- "$logpath" 53 | } 54 | 55 | rmqq() { 56 | logpath="$TMPDIR/q" 57 | if [[ -z "$TMPDIR" ]]; then 58 | logpath="/tmp/q" 59 | fi 60 | if [[ -f "$logpath" ]]; then 61 | rm "$logpath" 62 | fi 63 | qq 64 | } 65 | ``` 66 | 67 | You also can simply `tail -f $TMPDIR/q`, but it's highly recommended to use the above commands. 68 | 69 | ## Editor Integration 70 | 71 | ### VS Code 72 | 73 | `Preferences > User Snippets > Go` 74 | 75 | ```json 76 | "qq": { 77 | "prefix": "qq", 78 | "body": "q.Q($1) // DEBUG", 79 | "description": "Pretty-print to $TMPDIR/q" 80 | } 81 | ``` 82 | 83 | ### Sublime Text 84 | 85 | `Tools > Developer > New Snippet` 86 | 87 | ```xml 88 | 89 | 92 | qq 93 | source.go 94 | 95 | ``` 96 | 97 | ### Atom 98 | 99 | `Atom > Open Your Snippets` 100 | 101 | ```coffee 102 | '.source.go': 103 | 'qq': 104 | 'prefix': 'qq' 105 | 'body': 'q.Q($1) // DEBUG' 106 | ``` 107 | 108 | ### JetBrains GoLand 109 | 110 | `Settings > Editor > Live Templates` 111 | 112 | In `Go`, add a new template with: 113 | 114 | * Abbreviation: `qq` 115 | * Description: `Pretty-print to $TMPDIR/q` 116 | * Template text: `q.Q($END$) // DEBUG` 117 | * Applicable in: select the `Go` scope 118 | 119 | ### Emacs 120 | 121 | Add a new snippet file to the go-mode snippets directory 122 | (`$HOME/.emacs.d/snippets/go-mode/qq`). This should 123 | contain: 124 | 125 | ```emacs 126 | # -*- mode: snippet -*- 127 | # name: qq 128 | # key: qq 129 | # -- 130 | q.Q(${1:...}) // DEBUG 131 | ``` 132 | 133 | ### Vim/NeoVim 134 | 135 | For [SirVer/ultisnips](https://github.com/SirVer/ultisnips), use `:UltiSnipsEdit` to add the new snippet: 136 | 137 | ```snippets 138 | snippet qq "qq" 139 | q.Q(${1:}) 140 | ${2} 141 | endsnippet 142 | ``` 143 | 144 | ## Haven't I seen this somewhere before? 145 | 146 | Python programmers will recognize this as a Golang port of the 147 | [`q` module by zestyping](https://github.com/zestyping/q). 148 | 149 | Ping does a great job of explaining `q` in his awesome lightning talk from 150 | PyCon 2013. Watch it! It's funny :) 151 | 152 | [![ping's PyCon 2013 lightning talk](https://i.imgur.com/7KmWvtG.jpg)](https://youtu.be/OL3De8BAhME?t=25m14s) 153 | 154 | ## FAQ 155 | 156 | ### Why `q.Q`? 157 | 158 | It's quick to type and unlikely to cause naming collisions. 159 | 160 | ### Is `q.Q()` safe for concurrent use? 161 | 162 | Yes. 163 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Ryan Boehning. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package q 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "sync" 15 | "time" 16 | ) 17 | 18 | type color string 19 | 20 | const ( 21 | // ANSI color escape codes. 22 | bold color = "\033[1m" 23 | yellow color = "\033[33m" 24 | cyan color = "\033[36m" 25 | endColor color = "\033[0m" // "reset everything" 26 | 27 | maxLineWidth = 80 28 | ) 29 | 30 | // logger writes pretty logs to the $TMPDIR/q file. It takes care of opening and 31 | // closing the file. It is safe for concurrent use. 32 | type logger struct { 33 | mu sync.Mutex // protects all the other fields 34 | buf bytes.Buffer // collects writes before they're flushed to the log file 35 | start time.Time // time of first write in the current log group 36 | lastWrite time.Time // last time buffer was flushed. determines when to print header 37 | lastFile string // last file to call q.Q(). determines when to print header 38 | lastFunc string // last function to call q.Q(). determines when to print header 39 | } 40 | 41 | // header returns a formatted header string, e.g. [14:00:36 main.go main.main:122] 42 | // if the 2s timer has expired, or the calling function or filename has changed. 43 | // If none of those things are true, it returns an empty string. 44 | func (l *logger) header(funcName, file string, line int) string { 45 | if !l.shouldPrintHeader(funcName, file) { 46 | return "" 47 | } 48 | 49 | now := time.Now().UTC() 50 | l.start = now 51 | l.lastFunc = funcName 52 | l.lastFile = file 53 | 54 | return fmt.Sprintf("[%s %s:%d %s]", now.Format("15:04:05"), shortFile(file), line, funcName) 55 | } 56 | 57 | func (l *logger) shouldPrintHeader(funcName, file string) bool { 58 | if file != l.lastFile { 59 | return true 60 | } 61 | 62 | if funcName != l.lastFunc { 63 | return true 64 | } 65 | 66 | // If less than 2s has elapsed, this log line will be printed under the 67 | // previous header. 68 | const timeWindow = 2 * time.Second 69 | 70 | return time.Since(l.lastWrite) > timeWindow 71 | } 72 | 73 | // flush writes the logger's buffer to disk. 74 | func (l *logger) flush() (err error) { 75 | path := filepath.Join(os.TempDir(), "q") 76 | const userRW = 0o600 77 | f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, userRW) 78 | if err != nil { 79 | return fmt.Errorf("failed to open %q: %w", path, err) 80 | } 81 | defer func() { 82 | if cerr := f.Close(); err == nil { 83 | err = cerr 84 | } 85 | l.lastWrite = time.Now() 86 | }() 87 | 88 | _, err = io.Copy(f, &l.buf) 89 | l.buf.Reset() 90 | if err != nil { 91 | return fmt.Errorf("failed to flush q buffer: %w", err) 92 | } 93 | 94 | return nil 95 | } 96 | 97 | // output writes to the log buffer. Each log message is prepended with a 98 | // timestamp. Long lines are broken at 80 characters. 99 | func (l *logger) output(args ...string) { 100 | timestamp := fmt.Sprintf("%.3fs", time.Since(l.start).Seconds()) 101 | timestampWidth := len(timestamp) + 1 // +1 for padding space after timestamp 102 | timestamp = colorize(timestamp, yellow) 103 | 104 | // preWidth is the length of everything before the log message. 105 | fmt.Fprint(&l.buf, timestamp, " ") 106 | 107 | // Subsequent lines have to be indented by the width of the timestamp. 108 | indent := strings.Repeat(" ", timestampWidth) 109 | padding := "" // padding is the space between args. 110 | lineArgs := 0 // number of args printed on the current log line. 111 | lineWidth := timestampWidth 112 | for _, arg := range args { 113 | argWidth := argWidth(arg) 114 | lineWidth += argWidth + len(padding) 115 | 116 | // Some names in name=value strings contain newlines. Insert indentation 117 | // after each newline so they line up. 118 | arg = strings.ReplaceAll(arg, "\n", "\n"+indent) 119 | 120 | // Break up long lines. If this is first arg printed on the line 121 | // (lineArgs == 0), it makes no sense to break up the line. 122 | if lineWidth > maxLineWidth && lineArgs != 0 { 123 | fmt.Fprint(&l.buf, "\n", indent) 124 | lineArgs = 0 125 | lineWidth = timestampWidth + argWidth 126 | padding = "" 127 | } 128 | fmt.Fprint(&l.buf, padding, arg) 129 | lineArgs++ 130 | padding = " " 131 | } 132 | 133 | fmt.Fprint(&l.buf, "\n") 134 | } 135 | 136 | // shortFile takes an absolute file path and returns just the /, 137 | // e.g. "foo/bar.go". 138 | func shortFile(file string) string { 139 | dir := filepath.Base(filepath.Dir(file)) 140 | file = filepath.Base(file) 141 | 142 | return filepath.Join(dir, file) 143 | } 144 | -------------------------------------------------------------------------------- /args.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Ryan Boehning. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package q 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "go/ast" 11 | "go/parser" 12 | "go/printer" 13 | "go/token" 14 | "runtime" 15 | "strings" 16 | "unicode/utf8" 17 | 18 | "github.com/kr/pretty" 19 | ) 20 | 21 | // argName returns the source text of the given argument if it's a variable or 22 | // an expression. If the argument is something else, like a literal, argName 23 | // returns an empty string. 24 | func argName(arg ast.Expr) string { 25 | name := "" 26 | 27 | switch a := arg.(type) { 28 | case *ast.Ident: 29 | switch { 30 | case a.Obj == nil: 31 | name = a.Name 32 | case a.Obj.Kind == ast.Var, a.Obj.Kind == ast.Con: 33 | name = a.Obj.Name 34 | } 35 | case *ast.BinaryExpr, 36 | *ast.CallExpr, 37 | *ast.IndexExpr, 38 | *ast.KeyValueExpr, 39 | *ast.ParenExpr, 40 | *ast.SelectorExpr, 41 | *ast.SliceExpr, 42 | *ast.TypeAssertExpr, 43 | *ast.UnaryExpr: 44 | name = exprToString(arg) 45 | } 46 | 47 | return name 48 | } 49 | 50 | // argNames finds the q.Q() call at the given filename/line number and 51 | // returns its arguments as a slice of strings. If the argument is a literal, 52 | // argNames will return an empty string at the index position of that argument. 53 | // For example, q.Q(ip, port, 5432) would return []string{"ip", "port", ""}. 54 | // argNames returns an error if the source text cannot be parsed. 55 | func argNames(filename string, line int) ([]string, error) { 56 | fset := token.NewFileSet() 57 | f, err := parser.ParseFile(fset, filename, nil, 0) 58 | if err != nil { 59 | return nil, fmt.Errorf("failed to parse %q: %w", filename, err) 60 | } 61 | 62 | var names []string 63 | ast.Inspect(f, func(n ast.Node) bool { 64 | call, is := n.(*ast.CallExpr) 65 | if !is { 66 | // The node is not a function call. 67 | return true // visit next node 68 | } 69 | 70 | if fset.Position(call.End()).Line != line { 71 | // The node is a function call, but it's on the wrong line. 72 | return true 73 | } 74 | 75 | if !isQCall(call) { 76 | // The node is a function call on correct line, but it's not a Q() 77 | // function. 78 | return true 79 | } 80 | 81 | for _, arg := range call.Args { 82 | names = append(names, argName(arg)) 83 | } 84 | 85 | return true 86 | }) 87 | 88 | return names, nil 89 | } 90 | 91 | // argWidth returns the number of characters that will be seen when the given 92 | // argument is printed at the terminal. 93 | func argWidth(arg string) int { 94 | // Strip zero-width characters. 95 | replacer := strings.NewReplacer( 96 | "\n", "", 97 | "\t", "", 98 | "\r", "", 99 | "\f", "", 100 | "\v", "", 101 | string(bold), "", 102 | string(yellow), "", 103 | string(cyan), "", 104 | string(endColor), "", 105 | ) 106 | s := replacer.Replace(arg) 107 | 108 | return utf8.RuneCountInString(s) 109 | } 110 | 111 | // colorize returns the given text encapsulated in ANSI escape codes that 112 | // give the text color in the terminal. 113 | func colorize(text string, c color) string { 114 | return string(c) + text + string(endColor) 115 | } 116 | 117 | // exprToString returns the source text underlying the given ast.Expr. 118 | func exprToString(arg ast.Expr) string { 119 | var buf strings.Builder 120 | fset := token.NewFileSet() 121 | if err := printer.Fprint(&buf, fset, arg); err != nil { 122 | return "" 123 | } 124 | 125 | // CallExpr will be multi-line and indented with tabs. replace tabs with 126 | // spaces so we can better control formatting during output(). 127 | return strings.ReplaceAll(buf.String(), "\t", " ") 128 | } 129 | 130 | // formatArgs converts the given args to pretty-printed, colorized strings. 131 | func formatArgs(args ...interface{}) []string { 132 | formatted := make([]string, 0, len(args)) 133 | for _, a := range args { 134 | s := colorize(pretty.Sprint(a), cyan) 135 | formatted = append(formatted, s) 136 | } 137 | 138 | return formatted 139 | } 140 | 141 | // getCallerInfo returns the name, file, and line number of the function calling 142 | // q.Q(). 143 | func getCallerInfo() (funcName, file string, line int, err error) { 144 | pc, file, line, ok := runtime.Caller(CallDepth) 145 | if !ok { 146 | // This error is not exported. It is only used internally in the q 147 | // package. The error message isn't even used by the caller. So, I've 148 | // suppressed the err113 linter here, which catches nonidiomatic 149 | // error handling post Go 1.13 errors. 150 | return "", "", 0, errors.New("failed to get info about the function calling q.Q") // nolint: err113 151 | } 152 | 153 | funcName = runtime.FuncForPC(pc).Name() 154 | 155 | return funcName, file, line, nil 156 | } 157 | 158 | // prependArgName turns argument names and values into name=value strings, e.g. 159 | // "port=443", "3+2=5". If the name is given, it will be bolded using ANSI 160 | // color codes. If no name is given, just the value will be returned. 161 | func prependArgName(names, values []string) []string { 162 | prepended := make([]string, len(values)) 163 | for i, value := range values { 164 | name := "" 165 | if i < len(names) { 166 | name = names[i] 167 | } 168 | if name == "" { 169 | prepended[i] = value 170 | 171 | continue 172 | } 173 | name = colorize(name, bold) 174 | prepended[i] = fmt.Sprintf("%s=%s", name, value) 175 | } 176 | 177 | return prepended 178 | } 179 | 180 | // isQCall returns true if the given function call expression is Q() or q.Q(). 181 | func isQCall(n *ast.CallExpr) bool { 182 | return isQFunction(n) || isQPackage(n) 183 | } 184 | 185 | // isQFunction returns true if the given function call expression is Q(). 186 | func isQFunction(n *ast.CallExpr) bool { 187 | ident, is := n.Fun.(*ast.Ident) 188 | if !is { 189 | return false 190 | } 191 | 192 | return ident.Name == "Q" 193 | } 194 | 195 | // isQPackage returns true if the given function call expression is in the q 196 | // package. Since Q() is the only exported function from the q package, this is 197 | // sufficient for determining that we've found Q() in the source text. 198 | func isQPackage(n *ast.CallExpr) bool { 199 | sel, is := n.Fun.(*ast.SelectorExpr) // SelectorExpr example: a.B() 200 | if !is { 201 | return false 202 | } 203 | 204 | ident, is := sel.X.(*ast.Ident) // sel.X is the part that precedes the . 205 | if !is { 206 | return false 207 | } 208 | 209 | return ident.Name == "q" 210 | } 211 | -------------------------------------------------------------------------------- /vendor/github.com/rogpeppe/go-internal/fmtsort/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package fmtsort provides a general stable ordering mechanism 6 | // for maps, on behalf of the fmt and text/template packages. 7 | // It is not guaranteed to be efficient and works only for types 8 | // that are valid map keys. 9 | package fmtsort 10 | 11 | import ( 12 | "reflect" 13 | "sort" 14 | ) 15 | 16 | // Note: Throughout this package we avoid calling reflect.Value.Interface as 17 | // it is not always legal to do so and it's easier to avoid the issue than to face it. 18 | 19 | // SortedMap represents a map's keys and values. The keys and values are 20 | // aligned in index order: Value[i] is the value in the map corresponding to Key[i]. 21 | type SortedMap struct { 22 | Key []reflect.Value 23 | Value []reflect.Value 24 | } 25 | 26 | func (o *SortedMap) Len() int { return len(o.Key) } 27 | func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 } 28 | func (o *SortedMap) Swap(i, j int) { 29 | o.Key[i], o.Key[j] = o.Key[j], o.Key[i] 30 | o.Value[i], o.Value[j] = o.Value[j], o.Value[i] 31 | } 32 | 33 | // Sort accepts a map and returns a SortedMap that has the same keys and 34 | // values but in a stable sorted order according to the keys, modulo issues 35 | // raised by unorderable key values such as NaNs. 36 | // 37 | // The ordering rules are more general than with Go's < operator: 38 | // 39 | // - when applicable, nil compares low 40 | // - ints, floats, and strings order by < 41 | // - NaN compares less than non-NaN floats 42 | // - bool compares false before true 43 | // - complex compares real, then imag 44 | // - pointers compare by machine address 45 | // - channel values compare by machine address 46 | // - structs compare each field in turn 47 | // - arrays compare each element in turn. 48 | // Otherwise identical arrays compare by length. 49 | // - interface values compare first by reflect.Type describing the concrete type 50 | // and then by concrete value as described in the previous rules. 51 | func Sort(mapValue reflect.Value) *SortedMap { 52 | if mapValue.Type().Kind() != reflect.Map { 53 | return nil 54 | } 55 | key, value := mapElems(mapValue) 56 | sorted := &SortedMap{ 57 | Key: key, 58 | Value: value, 59 | } 60 | sort.Stable(sorted) 61 | return sorted 62 | } 63 | 64 | // compare compares two values of the same type. It returns -1, 0, 1 65 | // according to whether a > b (1), a == b (0), or a < b (-1). 66 | // If the types differ, it returns -1. 67 | // See the comment on Sort for the comparison rules. 68 | func compare(aVal, bVal reflect.Value) int { 69 | aType, bType := aVal.Type(), bVal.Type() 70 | if aType != bType { 71 | return -1 // No good answer possible, but don't return 0: they're not equal. 72 | } 73 | switch aVal.Kind() { 74 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 75 | a, b := aVal.Int(), bVal.Int() 76 | switch { 77 | case a < b: 78 | return -1 79 | case a > b: 80 | return 1 81 | default: 82 | return 0 83 | } 84 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 85 | a, b := aVal.Uint(), bVal.Uint() 86 | switch { 87 | case a < b: 88 | return -1 89 | case a > b: 90 | return 1 91 | default: 92 | return 0 93 | } 94 | case reflect.String: 95 | a, b := aVal.String(), bVal.String() 96 | switch { 97 | case a < b: 98 | return -1 99 | case a > b: 100 | return 1 101 | default: 102 | return 0 103 | } 104 | case reflect.Float32, reflect.Float64: 105 | return floatCompare(aVal.Float(), bVal.Float()) 106 | case reflect.Complex64, reflect.Complex128: 107 | a, b := aVal.Complex(), bVal.Complex() 108 | if c := floatCompare(real(a), real(b)); c != 0 { 109 | return c 110 | } 111 | return floatCompare(imag(a), imag(b)) 112 | case reflect.Bool: 113 | a, b := aVal.Bool(), bVal.Bool() 114 | switch { 115 | case a == b: 116 | return 0 117 | case a: 118 | return 1 119 | default: 120 | return -1 121 | } 122 | case reflect.Ptr: 123 | a, b := aVal.Pointer(), bVal.Pointer() 124 | switch { 125 | case a < b: 126 | return -1 127 | case a > b: 128 | return 1 129 | default: 130 | return 0 131 | } 132 | case reflect.Chan: 133 | if c, ok := nilCompare(aVal, bVal); ok { 134 | return c 135 | } 136 | ap, bp := aVal.Pointer(), bVal.Pointer() 137 | switch { 138 | case ap < bp: 139 | return -1 140 | case ap > bp: 141 | return 1 142 | default: 143 | return 0 144 | } 145 | case reflect.Struct: 146 | for i := range aVal.NumField() { 147 | if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 { 148 | return c 149 | } 150 | } 151 | return 0 152 | case reflect.Array: 153 | for i := range aVal.Len() { 154 | if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 { 155 | return c 156 | } 157 | } 158 | return 0 159 | case reflect.Interface: 160 | if c, ok := nilCompare(aVal, bVal); ok { 161 | return c 162 | } 163 | c := compare(reflect.ValueOf(aVal.Elem().Type()), reflect.ValueOf(bVal.Elem().Type())) 164 | if c != 0 { 165 | return c 166 | } 167 | return compare(aVal.Elem(), bVal.Elem()) 168 | default: 169 | // Certain types cannot appear as keys (maps, funcs, slices), but be explicit. 170 | panic("bad type in compare: " + aType.String()) 171 | } 172 | } 173 | 174 | // nilCompare checks whether either value is nil. If not, the boolean is false. 175 | // If either value is nil, the boolean is true and the integer is the comparison 176 | // value. The comparison is defined to be 0 if both are nil, otherwise the one 177 | // nil value compares low. Both arguments must represent a chan, func, 178 | // interface, map, pointer, or slice. 179 | func nilCompare(aVal, bVal reflect.Value) (int, bool) { 180 | if aVal.IsNil() { 181 | if bVal.IsNil() { 182 | return 0, true 183 | } 184 | return -1, true 185 | } 186 | if bVal.IsNil() { 187 | return 1, true 188 | } 189 | return 0, false 190 | } 191 | 192 | // floatCompare compares two floating-point values. NaNs compare low. 193 | func floatCompare(a, b float64) int { 194 | switch { 195 | case isNaN(a): 196 | return -1 // No good answer if b is a NaN so don't bother checking. 197 | case isNaN(b): 198 | return 1 199 | case a < b: 200 | return -1 201 | case a > b: 202 | return 1 203 | } 204 | return 0 205 | } 206 | 207 | func isNaN(a float64) bool { 208 | return a != a 209 | } 210 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/diff.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | ) 8 | 9 | type sbuf []string 10 | 11 | func (p *sbuf) Printf(format string, a ...interface{}) { 12 | s := fmt.Sprintf(format, a...) 13 | *p = append(*p, s) 14 | } 15 | 16 | // Diff returns a slice where each element describes 17 | // a difference between a and b. 18 | func Diff(a, b interface{}) (desc []string) { 19 | Pdiff((*sbuf)(&desc), a, b) 20 | return desc 21 | } 22 | 23 | // wprintfer calls Fprintf on w for each Printf call 24 | // with a trailing newline. 25 | type wprintfer struct{ w io.Writer } 26 | 27 | func (p *wprintfer) Printf(format string, a ...interface{}) { 28 | fmt.Fprintf(p.w, format+"\n", a...) 29 | } 30 | 31 | // Fdiff writes to w a description of the differences between a and b. 32 | func Fdiff(w io.Writer, a, b interface{}) { 33 | Pdiff(&wprintfer{w}, a, b) 34 | } 35 | 36 | type Printfer interface { 37 | Printf(format string, a ...interface{}) 38 | } 39 | 40 | // Pdiff prints to p a description of the differences between a and b. 41 | // It calls Printf once for each difference, with no trailing newline. 42 | // The standard library log.Logger is a Printfer. 43 | func Pdiff(p Printfer, a, b interface{}) { 44 | d := diffPrinter{ 45 | w: p, 46 | aVisited: make(map[visit]visit), 47 | bVisited: make(map[visit]visit), 48 | } 49 | d.diff(reflect.ValueOf(a), reflect.ValueOf(b)) 50 | } 51 | 52 | type Logfer interface { 53 | Logf(format string, a ...interface{}) 54 | } 55 | 56 | // logprintfer calls Fprintf on w for each Printf call 57 | // with a trailing newline. 58 | type logprintfer struct{ l Logfer } 59 | 60 | func (p *logprintfer) Printf(format string, a ...interface{}) { 61 | p.l.Logf(format, a...) 62 | } 63 | 64 | // Ldiff prints to l a description of the differences between a and b. 65 | // It calls Logf once for each difference, with no trailing newline. 66 | // The standard library testing.T and testing.B are Logfers. 67 | func Ldiff(l Logfer, a, b interface{}) { 68 | Pdiff(&logprintfer{l}, a, b) 69 | } 70 | 71 | type diffPrinter struct { 72 | w Printfer 73 | l string // label 74 | 75 | aVisited map[visit]visit 76 | bVisited map[visit]visit 77 | } 78 | 79 | func (w diffPrinter) printf(f string, a ...interface{}) { 80 | var l string 81 | if w.l != "" { 82 | l = w.l + ": " 83 | } 84 | w.w.Printf(l+f, a...) 85 | } 86 | 87 | func (w diffPrinter) diff(av, bv reflect.Value) { 88 | if !av.IsValid() && bv.IsValid() { 89 | w.printf("nil != %# v", formatter{v: bv, quote: true}) 90 | return 91 | } 92 | if av.IsValid() && !bv.IsValid() { 93 | w.printf("%# v != nil", formatter{v: av, quote: true}) 94 | return 95 | } 96 | if !av.IsValid() && !bv.IsValid() { 97 | return 98 | } 99 | 100 | at := av.Type() 101 | bt := bv.Type() 102 | if at != bt { 103 | w.printf("%v != %v", at, bt) 104 | return 105 | } 106 | 107 | if av.CanAddr() && bv.CanAddr() { 108 | avis := visit{av.UnsafeAddr(), at} 109 | bvis := visit{bv.UnsafeAddr(), bt} 110 | var cycle bool 111 | 112 | // Have we seen this value before? 113 | if vis, ok := w.aVisited[avis]; ok { 114 | cycle = true 115 | if vis != bvis { 116 | w.printf("%# v (previously visited) != %# v", formatter{v: av, quote: true}, formatter{v: bv, quote: true}) 117 | } 118 | } else if _, ok := w.bVisited[bvis]; ok { 119 | cycle = true 120 | w.printf("%# v != %# v (previously visited)", formatter{v: av, quote: true}, formatter{v: bv, quote: true}) 121 | } 122 | w.aVisited[avis] = bvis 123 | w.bVisited[bvis] = avis 124 | if cycle { 125 | return 126 | } 127 | } 128 | 129 | switch kind := at.Kind(); kind { 130 | case reflect.Bool: 131 | if a, b := av.Bool(), bv.Bool(); a != b { 132 | w.printf("%v != %v", a, b) 133 | } 134 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 135 | if a, b := av.Int(), bv.Int(); a != b { 136 | w.printf("%d != %d", a, b) 137 | } 138 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 139 | if a, b := av.Uint(), bv.Uint(); a != b { 140 | w.printf("%d != %d", a, b) 141 | } 142 | case reflect.Float32, reflect.Float64: 143 | if a, b := av.Float(), bv.Float(); a != b { 144 | w.printf("%v != %v", a, b) 145 | } 146 | case reflect.Complex64, reflect.Complex128: 147 | if a, b := av.Complex(), bv.Complex(); a != b { 148 | w.printf("%v != %v", a, b) 149 | } 150 | case reflect.Array: 151 | n := av.Len() 152 | for i := 0; i < n; i++ { 153 | w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i)) 154 | } 155 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 156 | if a, b := av.Pointer(), bv.Pointer(); a != b { 157 | w.printf("%#x != %#x", a, b) 158 | } 159 | case reflect.Interface: 160 | w.diff(av.Elem(), bv.Elem()) 161 | case reflect.Map: 162 | ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys()) 163 | for _, k := range ak { 164 | w := w.relabel(fmt.Sprintf("[%#v]", k)) 165 | w.printf("%q != (missing)", av.MapIndex(k)) 166 | } 167 | for _, k := range both { 168 | w := w.relabel(fmt.Sprintf("[%#v]", k)) 169 | w.diff(av.MapIndex(k), bv.MapIndex(k)) 170 | } 171 | for _, k := range bk { 172 | w := w.relabel(fmt.Sprintf("[%#v]", k)) 173 | w.printf("(missing) != %q", bv.MapIndex(k)) 174 | } 175 | case reflect.Ptr: 176 | switch { 177 | case av.IsNil() && !bv.IsNil(): 178 | w.printf("nil != %# v", formatter{v: bv, quote: true}) 179 | case !av.IsNil() && bv.IsNil(): 180 | w.printf("%# v != nil", formatter{v: av, quote: true}) 181 | case !av.IsNil() && !bv.IsNil(): 182 | w.diff(av.Elem(), bv.Elem()) 183 | } 184 | case reflect.Slice: 185 | lenA := av.Len() 186 | lenB := bv.Len() 187 | if lenA != lenB { 188 | w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB) 189 | break 190 | } 191 | for i := 0; i < lenA; i++ { 192 | w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i)) 193 | } 194 | case reflect.String: 195 | if a, b := av.String(), bv.String(); a != b { 196 | w.printf("%q != %q", a, b) 197 | } 198 | case reflect.Struct: 199 | for i := 0; i < av.NumField(); i++ { 200 | w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i)) 201 | } 202 | default: 203 | panic("unknown reflect Kind: " + kind.String()) 204 | } 205 | } 206 | 207 | func (d diffPrinter) relabel(name string) (d1 diffPrinter) { 208 | d1 = d 209 | if d.l != "" && name[0] != '[' { 210 | d1.l += "." 211 | } 212 | d1.l += name 213 | return d1 214 | } 215 | 216 | // keyEqual compares a and b for equality. 217 | // Both a and b must be valid map keys. 218 | func keyEqual(av, bv reflect.Value) bool { 219 | if !av.IsValid() && !bv.IsValid() { 220 | return true 221 | } 222 | if !av.IsValid() || !bv.IsValid() || av.Type() != bv.Type() { 223 | return false 224 | } 225 | switch kind := av.Kind(); kind { 226 | case reflect.Bool: 227 | a, b := av.Bool(), bv.Bool() 228 | return a == b 229 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 230 | a, b := av.Int(), bv.Int() 231 | return a == b 232 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 233 | a, b := av.Uint(), bv.Uint() 234 | return a == b 235 | case reflect.Float32, reflect.Float64: 236 | a, b := av.Float(), bv.Float() 237 | return a == b 238 | case reflect.Complex64, reflect.Complex128: 239 | a, b := av.Complex(), bv.Complex() 240 | return a == b 241 | case reflect.Array: 242 | for i := 0; i < av.Len(); i++ { 243 | if !keyEqual(av.Index(i), bv.Index(i)) { 244 | return false 245 | } 246 | } 247 | return true 248 | case reflect.Chan, reflect.UnsafePointer, reflect.Ptr: 249 | a, b := av.Pointer(), bv.Pointer() 250 | return a == b 251 | case reflect.Interface: 252 | return keyEqual(av.Elem(), bv.Elem()) 253 | case reflect.String: 254 | a, b := av.String(), bv.String() 255 | return a == b 256 | case reflect.Struct: 257 | for i := 0; i < av.NumField(); i++ { 258 | if !keyEqual(av.Field(i), bv.Field(i)) { 259 | return false 260 | } 261 | } 262 | return true 263 | default: 264 | panic("invalid map key type " + av.Type().String()) 265 | } 266 | } 267 | 268 | func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { 269 | for _, av := range a { 270 | inBoth := false 271 | for _, bv := range b { 272 | if keyEqual(av, bv) { 273 | inBoth = true 274 | both = append(both, av) 275 | break 276 | } 277 | } 278 | if !inBoth { 279 | ak = append(ak, av) 280 | } 281 | } 282 | for _, bv := range b { 283 | inBoth := false 284 | for _, av := range a { 285 | if keyEqual(av, bv) { 286 | inBoth = true 287 | break 288 | } 289 | } 290 | if !inBoth { 291 | bk = append(bk, bv) 292 | } 293 | } 294 | return 295 | } 296 | -------------------------------------------------------------------------------- /vendor/github.com/kr/pretty/formatter.go: -------------------------------------------------------------------------------- 1 | package pretty 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | "strconv" 8 | "text/tabwriter" 9 | 10 | "github.com/kr/text" 11 | "github.com/rogpeppe/go-internal/fmtsort" 12 | ) 13 | 14 | type formatter struct { 15 | v reflect.Value 16 | force bool 17 | quote bool 18 | } 19 | 20 | // Formatter makes a wrapper, f, that will format x as go source with line 21 | // breaks and tabs. Object f responds to the "%v" formatting verb when both the 22 | // "#" and " " (space) flags are set, for example: 23 | // 24 | // fmt.Sprintf("%# v", Formatter(x)) 25 | // 26 | // If one of these two flags is not set, or any other verb is used, f will 27 | // format x according to the usual rules of package fmt. 28 | // In particular, if x satisfies fmt.Formatter, then x.Format will be called. 29 | func Formatter(x interface{}) (f fmt.Formatter) { 30 | return formatter{v: reflect.ValueOf(x), quote: true} 31 | } 32 | 33 | func (fo formatter) String() string { 34 | return fmt.Sprint(fo.v.Interface()) // unwrap it 35 | } 36 | 37 | func (fo formatter) passThrough(f fmt.State, c rune) { 38 | s := "%" 39 | for i := 0; i < 128; i++ { 40 | if f.Flag(i) { 41 | s += string(rune(i)) 42 | } 43 | } 44 | if w, ok := f.Width(); ok { 45 | s += fmt.Sprintf("%d", w) 46 | } 47 | if p, ok := f.Precision(); ok { 48 | s += fmt.Sprintf(".%d", p) 49 | } 50 | s += string(c) 51 | fmt.Fprintf(f, s, fo.v.Interface()) 52 | } 53 | 54 | func (fo formatter) Format(f fmt.State, c rune) { 55 | if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { 56 | w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) 57 | p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} 58 | p.printValue(fo.v, true, fo.quote) 59 | w.Flush() 60 | return 61 | } 62 | fo.passThrough(f, c) 63 | } 64 | 65 | type printer struct { 66 | io.Writer 67 | tw *tabwriter.Writer 68 | visited map[visit]int 69 | depth int 70 | } 71 | 72 | func (p *printer) indent() *printer { 73 | q := *p 74 | q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) 75 | q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) 76 | return &q 77 | } 78 | 79 | func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { 80 | if showType { 81 | io.WriteString(p, v.Type().String()) 82 | fmt.Fprintf(p, "(%#v)", x) 83 | } else { 84 | fmt.Fprintf(p, "%#v", x) 85 | } 86 | } 87 | 88 | // printValue must keep track of already-printed pointer values to avoid 89 | // infinite recursion. 90 | type visit struct { 91 | v uintptr 92 | typ reflect.Type 93 | } 94 | 95 | func (p *printer) catchPanic(v reflect.Value, method string) { 96 | if r := recover(); r != nil { 97 | if v.Kind() == reflect.Ptr && v.IsNil() { 98 | writeByte(p, '(') 99 | io.WriteString(p, v.Type().String()) 100 | io.WriteString(p, ")(nil)") 101 | return 102 | } 103 | writeByte(p, '(') 104 | io.WriteString(p, v.Type().String()) 105 | io.WriteString(p, ")(PANIC=calling method ") 106 | io.WriteString(p, strconv.Quote(method)) 107 | io.WriteString(p, ": ") 108 | fmt.Fprint(p, r) 109 | writeByte(p, ')') 110 | } 111 | } 112 | 113 | func (p *printer) printValue(v reflect.Value, showType, quote bool) { 114 | if p.depth > 10 { 115 | io.WriteString(p, "!%v(DEPTH EXCEEDED)") 116 | return 117 | } 118 | 119 | if v.IsValid() && v.CanInterface() { 120 | i := v.Interface() 121 | if goStringer, ok := i.(fmt.GoStringer); ok { 122 | defer p.catchPanic(v, "GoString") 123 | io.WriteString(p, goStringer.GoString()) 124 | return 125 | } 126 | } 127 | 128 | switch v.Kind() { 129 | case reflect.Bool: 130 | p.printInline(v, v.Bool(), showType) 131 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 132 | p.printInline(v, v.Int(), showType) 133 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 134 | p.printInline(v, v.Uint(), showType) 135 | case reflect.Float32, reflect.Float64: 136 | p.printInline(v, v.Float(), showType) 137 | case reflect.Complex64, reflect.Complex128: 138 | fmt.Fprintf(p, "%#v", v.Complex()) 139 | case reflect.String: 140 | p.fmtString(v.String(), quote) 141 | case reflect.Map: 142 | t := v.Type() 143 | if showType { 144 | io.WriteString(p, t.String()) 145 | } 146 | writeByte(p, '{') 147 | if nonzero(v) { 148 | expand := !canInline(v.Type()) 149 | pp := p 150 | if expand { 151 | writeByte(p, '\n') 152 | pp = p.indent() 153 | } 154 | sm := fmtsort.Sort(v) 155 | for i := 0; i < v.Len(); i++ { 156 | k := sm.Key[i] 157 | mv := sm.Value[i] 158 | pp.printValue(k, false, true) 159 | writeByte(pp, ':') 160 | if expand { 161 | writeByte(pp, '\t') 162 | } 163 | showTypeInStruct := t.Elem().Kind() == reflect.Interface 164 | pp.printValue(mv, showTypeInStruct, true) 165 | if expand { 166 | io.WriteString(pp, ",\n") 167 | } else if i < v.Len()-1 { 168 | io.WriteString(pp, ", ") 169 | } 170 | } 171 | if expand { 172 | pp.tw.Flush() 173 | } 174 | } 175 | writeByte(p, '}') 176 | case reflect.Struct: 177 | t := v.Type() 178 | if v.CanAddr() { 179 | addr := v.UnsafeAddr() 180 | vis := visit{addr, t} 181 | if vd, ok := p.visited[vis]; ok && vd < p.depth { 182 | p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false) 183 | break // don't print v again 184 | } 185 | p.visited[vis] = p.depth 186 | } 187 | 188 | if showType { 189 | io.WriteString(p, t.String()) 190 | } 191 | writeByte(p, '{') 192 | if nonzero(v) { 193 | expand := !canInline(v.Type()) 194 | pp := p 195 | if expand { 196 | writeByte(p, '\n') 197 | pp = p.indent() 198 | } 199 | for i := 0; i < v.NumField(); i++ { 200 | showTypeInStruct := true 201 | if f := t.Field(i); f.Name != "" { 202 | io.WriteString(pp, f.Name) 203 | writeByte(pp, ':') 204 | if expand { 205 | writeByte(pp, '\t') 206 | } 207 | showTypeInStruct = labelType(f.Type) 208 | } 209 | pp.printValue(getField(v, i), showTypeInStruct, true) 210 | if expand { 211 | io.WriteString(pp, ",\n") 212 | } else if i < v.NumField()-1 { 213 | io.WriteString(pp, ", ") 214 | } 215 | } 216 | if expand { 217 | pp.tw.Flush() 218 | } 219 | } 220 | writeByte(p, '}') 221 | case reflect.Interface: 222 | switch e := v.Elem(); { 223 | case e.Kind() == reflect.Invalid: 224 | io.WriteString(p, "nil") 225 | case e.IsValid(): 226 | pp := *p 227 | pp.depth++ 228 | pp.printValue(e, showType, true) 229 | default: 230 | io.WriteString(p, v.Type().String()) 231 | io.WriteString(p, "(nil)") 232 | } 233 | case reflect.Array, reflect.Slice: 234 | t := v.Type() 235 | if showType { 236 | io.WriteString(p, t.String()) 237 | } 238 | if v.Kind() == reflect.Slice && v.IsNil() && showType { 239 | io.WriteString(p, "(nil)") 240 | break 241 | } 242 | if v.Kind() == reflect.Slice && v.IsNil() { 243 | io.WriteString(p, "nil") 244 | break 245 | } 246 | writeByte(p, '{') 247 | expand := !canInline(v.Type()) 248 | pp := p 249 | if expand { 250 | writeByte(p, '\n') 251 | pp = p.indent() 252 | } 253 | for i := 0; i < v.Len(); i++ { 254 | showTypeInSlice := t.Elem().Kind() == reflect.Interface 255 | pp.printValue(v.Index(i), showTypeInSlice, true) 256 | if expand { 257 | io.WriteString(pp, ",\n") 258 | } else if i < v.Len()-1 { 259 | io.WriteString(pp, ", ") 260 | } 261 | } 262 | if expand { 263 | pp.tw.Flush() 264 | } 265 | writeByte(p, '}') 266 | case reflect.Ptr: 267 | e := v.Elem() 268 | if !e.IsValid() { 269 | writeByte(p, '(') 270 | io.WriteString(p, v.Type().String()) 271 | io.WriteString(p, ")(nil)") 272 | } else { 273 | pp := *p 274 | pp.depth++ 275 | writeByte(pp, '&') 276 | pp.printValue(e, true, true) 277 | } 278 | case reflect.Chan: 279 | x := v.Pointer() 280 | if showType { 281 | writeByte(p, '(') 282 | io.WriteString(p, v.Type().String()) 283 | fmt.Fprintf(p, ")(%#v)", x) 284 | } else { 285 | fmt.Fprintf(p, "%#v", x) 286 | } 287 | case reflect.Func: 288 | io.WriteString(p, v.Type().String()) 289 | io.WriteString(p, " {...}") 290 | case reflect.UnsafePointer: 291 | p.printInline(v, v.Pointer(), showType) 292 | case reflect.Invalid: 293 | io.WriteString(p, "nil") 294 | } 295 | } 296 | 297 | func canInline(t reflect.Type) bool { 298 | switch t.Kind() { 299 | case reflect.Map: 300 | return !canExpand(t.Elem()) 301 | case reflect.Struct: 302 | for i := 0; i < t.NumField(); i++ { 303 | if canExpand(t.Field(i).Type) { 304 | return false 305 | } 306 | } 307 | return true 308 | case reflect.Interface: 309 | return false 310 | case reflect.Array, reflect.Slice: 311 | return !canExpand(t.Elem()) 312 | case reflect.Ptr: 313 | return false 314 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 315 | return false 316 | } 317 | return true 318 | } 319 | 320 | func canExpand(t reflect.Type) bool { 321 | switch t.Kind() { 322 | case reflect.Map, reflect.Struct, 323 | reflect.Interface, reflect.Array, reflect.Slice, 324 | reflect.Ptr: 325 | return true 326 | } 327 | return false 328 | } 329 | 330 | func labelType(t reflect.Type) bool { 331 | switch t.Kind() { 332 | case reflect.Interface, reflect.Struct: 333 | return true 334 | } 335 | return false 336 | } 337 | 338 | func (p *printer) fmtString(s string, quote bool) { 339 | if quote { 340 | s = strconv.Quote(s) 341 | } 342 | io.WriteString(p, s) 343 | } 344 | 345 | func writeByte(w io.Writer, b byte) { 346 | w.Write([]byte{b}) 347 | } 348 | 349 | func getField(v reflect.Value, i int) reflect.Value { 350 | val := v.Field(i) 351 | if val.Kind() == reflect.Interface && !val.IsNil() { 352 | val = val.Elem() 353 | } 354 | return val 355 | } 356 | -------------------------------------------------------------------------------- /args_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Ryan Boehning. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package q 6 | 7 | import ( 8 | "fmt" 9 | "go/ast" 10 | "go/token" 11 | "testing" 12 | 13 | "github.com/kr/pretty" 14 | ) 15 | 16 | // TestExtractingArgsFromSourceText verifies that exprToString() and argName() 17 | // arg able to extract the text of the arguments passed to q.Q(). For example, 18 | // q.Q(myVar) should return "myVar". 19 | // nolint: funlen,maintidx 20 | func TestExtractingArgsFromSourceText(t *testing.T) { 21 | testCases := []struct { 22 | id int 23 | arg ast.Expr 24 | want string 25 | }{ 26 | { 27 | id: 1, 28 | arg: &ast.Ident{Name: "myVar"}, 29 | want: "myVar", 30 | }, 31 | { 32 | id: 2, 33 | arg: &ast.Ident{Name: "awesomeVar"}, 34 | want: "awesomeVar", 35 | }, 36 | { 37 | id: 3, 38 | arg: &ast.Ident{Name: "myVar"}, 39 | want: "myVar", 40 | }, 41 | { 42 | id: 4, 43 | arg: &ast.Ident{Name: "myVar"}, 44 | want: "myVar", 45 | }, 46 | { 47 | id: 5, 48 | arg: &ast.BinaryExpr{ 49 | X: &ast.BasicLit{Kind: token.INT, Value: "1"}, 50 | Op: token.ADD, 51 | Y: &ast.BasicLit{Kind: token.INT, Value: "2"}, 52 | }, 53 | want: "1 + 2", 54 | }, 55 | { 56 | id: 6, 57 | arg: &ast.BinaryExpr{ 58 | X: &ast.BasicLit{Kind: token.FLOAT, Value: "3.14"}, 59 | Op: token.QUO, 60 | Y: &ast.BasicLit{Kind: token.FLOAT, Value: "1.59"}, 61 | }, 62 | want: "3.14 / 1.59", 63 | }, 64 | { 65 | id: 7, 66 | arg: &ast.BinaryExpr{ 67 | X: &ast.BasicLit{Kind: token.INT, Value: "123"}, 68 | Op: token.MUL, 69 | Y: &ast.BasicLit{Kind: token.INT, Value: "234"}, 70 | }, 71 | want: "123 * 234", 72 | }, 73 | { 74 | id: 8, 75 | arg: &ast.CallExpr{ 76 | Fun: &ast.Ident{ 77 | Name: "foo", 78 | }, 79 | Lparen: token.NoPos, 80 | Args: nil, 81 | Rparen: token.NoPos, 82 | }, 83 | want: "foo()", 84 | }, 85 | { 86 | id: 9, 87 | arg: &ast.IndexExpr{ 88 | X: &ast.Ident{ 89 | Name: "a", 90 | }, 91 | Index: &ast.BasicLit{Kind: token.INT, Value: "1"}, 92 | }, 93 | want: "a[1]", 94 | }, 95 | { 96 | id: 10, 97 | arg: &ast.KeyValueExpr{ 98 | Key: &ast.Ident{ 99 | Name: "Greeting", 100 | }, 101 | Value: &ast.BasicLit{Kind: token.STRING, Value: "\"Hello\""}, 102 | }, 103 | want: `Greeting: "Hello"`, 104 | }, 105 | { 106 | id: 11, 107 | arg: &ast.ParenExpr{ 108 | X: &ast.BinaryExpr{ 109 | X: &ast.BasicLit{Kind: token.INT, Value: "2"}, 110 | Op: token.MUL, 111 | Y: &ast.BasicLit{Kind: token.INT, Value: "3"}, 112 | }, 113 | }, 114 | want: "(2 * 3)", 115 | }, 116 | { 117 | id: 12, 118 | arg: &ast.SelectorExpr{ 119 | X: &ast.Ident{ 120 | Name: "fmt", 121 | }, 122 | Sel: &ast.Ident{ 123 | Name: "Print", 124 | }, 125 | }, 126 | want: "fmt.Print", 127 | }, 128 | { 129 | id: 13, 130 | arg: &ast.SliceExpr{ 131 | X: &ast.Ident{ 132 | Name: "a", 133 | }, 134 | Low: &ast.BasicLit{Kind: token.INT, Value: "0"}, 135 | High: &ast.BasicLit{Kind: token.INT, Value: "2"}, 136 | Max: nil, 137 | Slice3: false, 138 | }, 139 | want: "a[0:2]", 140 | }, 141 | { 142 | id: 14, 143 | arg: &ast.TypeAssertExpr{ 144 | X: &ast.Ident{ 145 | Name: "a", 146 | }, 147 | Type: &ast.Ident{ 148 | Name: "string", 149 | }, 150 | }, 151 | want: "a.(string)", 152 | }, 153 | { 154 | id: 15, 155 | arg: &ast.UnaryExpr{ 156 | Op: token.SUB, 157 | X: &ast.BasicLit{Kind: token.INT, Value: "1"}, 158 | }, 159 | want: "-1", 160 | }, 161 | { 162 | id: 16, 163 | arg: &ast.Ident{ 164 | Name: "string", 165 | }, 166 | want: "string", 167 | }, 168 | } 169 | 170 | // We can test both exprToString() and argName() with the test cases above. 171 | for _, tc := range testCases { 172 | // test exprToString() 173 | testName := fmt.Sprintf("exprToString(%T)", tc.arg) 174 | // nolint: thelper 175 | t.Run(testName, func(t *testing.T) { 176 | if _, ok := tc.arg.(*ast.Ident); ok { 177 | return 178 | } 179 | 180 | if got := exprToString(tc.arg); got != tc.want { 181 | t.Fatalf("\ngot: %s\nwant: %s", got, tc.want) 182 | } 183 | }) 184 | 185 | // test argName() 186 | testName = fmt.Sprintf("argName(%T)", tc.arg) 187 | // nolint: thelper 188 | t.Run(testName, func(t *testing.T) { 189 | if got := argName(tc.arg); got != tc.want { 190 | t.Fatalf("\ngot: %s\nwant: %s", got, tc.want) 191 | } 192 | }) 193 | } 194 | } 195 | 196 | // TestArgNames verifies that argNames() is able to find the q.Q() call in the 197 | // sample text and extract the argument names. For example, if q.q(a, b, c) is 198 | // in the sample text, argNames() should return []string{"a", "b", "c"}. 199 | func TestArgNames(t *testing.T) { 200 | const filename = "testdata/sample1.go" 201 | want := []string{"a", "b", "c", "d", "e", "f", "g"} 202 | got, err := argNames(filename, 14) 203 | if err != nil { 204 | t.Fatalf("argNames: failed to parse %q: %v", filename, err) 205 | } 206 | 207 | if len(got) != len(want) { 208 | t.Fatalf("\ngot: %#v\nwant: %#v", got, want) 209 | } 210 | 211 | for i := range got { 212 | if got[i] != want[i] { 213 | t.Fatalf("\ngot: %#v\nwant: %#v", got, want) 214 | } 215 | } 216 | } 217 | 218 | // TestArgNamesBadFilename verifies that argNames() returns an error if given an 219 | // invalid filename. 220 | func TestArgNamesBadFilename(t *testing.T) { 221 | const badFilename = "BAD FILENAME" 222 | _, err := argNames(badFilename, 666) 223 | if err == nil { 224 | t.Fatalf("\nargNames(%s)\ngot: err == nil\nwant: err != nil", badFilename) 225 | } 226 | } 227 | 228 | // TestArgWidth verifies that argWidth() returns the correct number of printable 229 | // characters in a string. 230 | func TestArgWidth(t *testing.T) { 231 | testCases := []struct { 232 | arg string 233 | wantWidth int 234 | }{ 235 | {colorize("myVar", cyan), 5}, 236 | {colorize(`"myStringLiteral"`, cyan), 17}, 237 | {colorize("func (n int) { return n > 0 }(1)", cyan), 32}, 238 | {colorize("myVar", bold), 5}, 239 | {colorize("3.14", cyan), 4}, 240 | {colorize("你好", cyan), 2}, 241 | } 242 | 243 | for _, tc := range testCases { 244 | gotWidth := argWidth(tc.arg) 245 | if gotWidth != tc.wantWidth { 246 | t.Fatalf("\nargWidth(%s)\ngot: %d\nwant: %d", tc.arg, gotWidth, tc.wantWidth) 247 | } 248 | } 249 | } 250 | 251 | // TestFormatArgs verifies that formatArgs() produces the expected string. 252 | func TestFormatArgs(t *testing.T) { 253 | testCases := []struct { 254 | id int 255 | args []interface{} 256 | want []string 257 | }{ 258 | { 259 | id: 1, 260 | args: []interface{}{123}, 261 | want: []string{colorize("int(123)", cyan)}, 262 | }, 263 | { 264 | id: 2, 265 | args: []interface{}{123, 3.14, "hello world"}, 266 | want: []string{ 267 | colorize("int(123)", cyan), 268 | colorize("float64(3.14)", cyan), 269 | colorize("hello world", cyan), 270 | }, 271 | }, 272 | { 273 | id: 3, 274 | args: []interface{}{[]string{"goodbye", "world"}}, 275 | want: []string{ 276 | colorize(`[]string{"goodbye", "world"}`, cyan), 277 | }, 278 | }, 279 | { 280 | id: 4, 281 | args: []interface{}{ 282 | []struct{ a, b int }{ 283 | {1, 2}, {2, 3}, {3, 4}, 284 | }, 285 | }, 286 | want: []string{ 287 | colorize(`[]struct { a int; b int }{ 288 | {a:1, b:2}, 289 | {a:2, b:3}, 290 | {a:3, b:4}, 291 | }`, cyan), 292 | }, 293 | }, 294 | } 295 | 296 | for _, tc := range testCases { 297 | got := formatArgs(tc.args...) 298 | 299 | if len(got) != len(tc.want) { 300 | t.Fatalf("\nTEST %d\ngot: %s\nwant: %s", tc.id, got, tc.want) 301 | } 302 | 303 | for i := range got { 304 | if got[i] != tc.want[i] { 305 | t.Fatalf("\nTEST %d\ngot: %s\nwant: %s", tc.id, got, tc.want) 306 | } 307 | } 308 | } 309 | } 310 | 311 | // TestPrependArgName verifies that prependArgName() correctly merges a slice of 312 | // variable names and a slice of variabe values into name=value strings. 313 | func TestPrependArgName(t *testing.T) { 314 | testCases := []struct { 315 | names []string 316 | values []string 317 | want []string 318 | }{ 319 | { 320 | names: []string{"myVar"}, 321 | values: []string{colorize("int(100)", cyan)}, 322 | want: []string{fmt.Sprintf("%s=%s", colorize("myVar", bold), colorize("int(100)", cyan))}, 323 | }, 324 | { 325 | names: []string{"", "myFloat"}, 326 | values: []string{colorize("hello", cyan), colorize("float64(3.14)", cyan)}, 327 | want: []string{ 328 | colorize("hello", cyan), 329 | fmt.Sprintf("%s=%s", colorize("myFloat", bold), colorize("float64(3.14)", cyan)), 330 | }, 331 | }, 332 | { 333 | names: []string{"myStructSlice", "", "myFunc"}, 334 | values: []string{ 335 | colorize("[]*Foo{&Foo{123, 234}, &Foo{345, 456}}", cyan), 336 | colorize("int(-666)", cyan), 337 | colorize("func (n int) bool { return n > 0 }", cyan), 338 | }, 339 | want: []string{ 340 | fmt.Sprintf("%s=%s", colorize("myStructSlice", bold), colorize("[]*Foo{&Foo{123, 234}, &Foo{345, 456}}", cyan)), 341 | colorize("int(-666)", cyan), 342 | fmt.Sprintf("%s=%s", colorize("myFunc", bold), colorize("func (n int) bool { return n > 0 }", cyan)), 343 | }, 344 | }, 345 | } 346 | 347 | for _, tc := range testCases { 348 | got := prependArgName(tc.names, tc.values) 349 | if len(got) != len(tc.want) { 350 | t.Fatalf("\nprependArgName(%v, %v)\ngot: %v\nwant: %v", tc.names, tc.values, got, tc.want) 351 | } 352 | 353 | for i := range got { 354 | if got[i] != tc.want[i] { 355 | t.Fatalf("\nprependArgName(%v, %v)\ngot: %v\nwant: %v", tc.names, tc.values, got, tc.want) 356 | } 357 | } 358 | } 359 | } 360 | 361 | // TestIsQCall verifies that isQCall() returns true if the given call expression 362 | // is q.Q(). 363 | // nolint: funlen 364 | func TestIsQCall(t *testing.T) { 365 | testCases := []struct { 366 | id int 367 | expr *ast.CallExpr 368 | want bool 369 | }{ 370 | { 371 | id: 1, 372 | expr: &ast.CallExpr{ 373 | Fun: &ast.Ident{Name: "Q"}, 374 | }, 375 | want: true, 376 | }, 377 | { 378 | id: 2, 379 | expr: &ast.CallExpr{ 380 | Fun: &ast.Ident{Name: "R"}, 381 | }, 382 | want: false, 383 | }, 384 | { 385 | id: 3, 386 | expr: &ast.CallExpr{ 387 | Fun: &ast.SelectorExpr{ 388 | X: &ast.Ident{Name: "q"}, 389 | }, 390 | }, 391 | want: true, 392 | }, 393 | { 394 | id: 4, 395 | expr: &ast.CallExpr{ 396 | Fun: &ast.SelectorExpr{ 397 | X: &ast.Ident{Name: "Q"}, 398 | }, 399 | }, 400 | want: false, 401 | }, 402 | { 403 | id: 5, 404 | expr: &ast.CallExpr{ 405 | Fun: &ast.SelectorExpr{ 406 | X: &ast.BadExpr{}, 407 | }, 408 | }, 409 | want: false, 410 | }, 411 | { 412 | id: 6, 413 | expr: &ast.CallExpr{ 414 | Fun: &ast.Ident{Name: "q"}, 415 | }, 416 | want: false, 417 | }, 418 | } 419 | 420 | for _, tc := range testCases { 421 | got := isQCall(tc.expr) 422 | if got != tc.want { 423 | t.Fatalf( 424 | "\nTEST %d\nisQCall(%s)\ngot: %v\nwant: %v", 425 | tc.id, 426 | pretty.Sprint(tc.expr), 427 | got, 428 | tc.want, 429 | ) 430 | } 431 | } 432 | } 433 | --------------------------------------------------------------------------------