├── .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 | [](https://goreportcard.com/report/github.com/ryboe/q)
4 | [](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 | 
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 | [](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 |
--------------------------------------------------------------------------------