Skip to main content

q.go

1package q
3import (
4 "bufio"
5 "fmt"
6 "os"
7 "regexp"
8 "runtime"
9 "strings"
12// Choose how to write the function name in the file.
13//
14// e.g. test functions can be long and have multiple parts, so we just
15// pull out the most meaningful part.
16func chooseDisplayName(functionName string) string {
17 parts := strings.Split(functionName, ".")
19 // If this is an anonymous function, the name will be something like
20 // "func1", which is unhelpful.
21 //
22 // Throw away that part and get the next part.
23 if m, _ := regexp.MatchString("^func[0-9]+$", parts[len(parts)-1]); len(parts) > 1 && m {
24 fmt.Println(m)
25 return parts[len(parts)-2]
26 }
28 return parts[len(parts)-1]
31func getFunctionName() string {
32 pc, _, _, ok := runtime.Caller(2)
33 if !ok {
34 return "<unknown>"
35 }
37 fn := runtime.FuncForPC(pc)
38 if fn == nil {
39 return "<unknown>"
40 }
42 return chooseDisplayName(fn.Name())
45func getExpression() string {
46 _, file, line, ok := runtime.Caller(2)
47 if !ok {
48 return "<unknown>"
49 }
51 f, err := os.Open(file)
52 if err != nil {
53 return "<unknown>"
54 }
55 defer f.Close()
57 scanner := bufio.NewScanner(f)
58 currentLine := 1
60 for scanner.Scan() {
61 if currentLine == line {
62 thisLine := strings.TrimSpace(scanner.Text())
63 thisLine, _ = strings.CutPrefix(thisLine, "q.Q(")
64 thisLine, _ = strings.CutSuffix(thisLine, ")")
65 return thisLine
66 }
67 currentLine++
68 }
70 if err := scanner.Err(); err != nil {
71 return "<unknown>"
72 }
74 return "<unknown>"
77func toString(value any, a ...any) string {
78 switch v := value.(type) {
79 case string:
80 if len(a) == 0 {
81 return fmt.Sprintf("%q", v)
82 } else {
83 v = strings.ReplaceAll(v, "%+v", "\x1b[39m%+v\x1b[39m")
84 v = strings.ReplaceAll(v, "%v", "\x1b[39m%v\x1b[39m")
85 v = strings.ReplaceAll(v, "%t", "\x1b[39m%t\x1b[39m")
86 return fmt.Sprintf(v, a...)
87 }
88 case int, int8, int16, int32, int64,
89 uint, uint8, uint16, uint32, uint64,
90 float32, float64, bool:
91 return fmt.Sprintf("%v", v)
92 case fmt.Stringer:
93 return v.String()
94 default:
95 return fmt.Sprintf("%+v", v) // fallback
96 }
99func Q(value any, a ...any) {
100 f, err := os.OpenFile("/tmp/q.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
101 if err != nil {
102 panic(err)
103 }
104 defer f.Close()
106 functionName := getFunctionName()
107 expression := getExpression()
109 var line string
111 if expression[0] == '"' && expression[len(expression)-1] == '"' {
112 line = "\x1b[32m" + functionName + "\x1b[39m: " + toString(value, a...) + "\n\n"
113 } else {
114 line = "\x1b[32m" + functionName + "\x1b[39m: " + expression + " = \x1b[36m" + toString(value, a...) + "\x1b[39m\n\n"
115 }
117 if _, err = f.WriteString(line); err != nil {
118 panic(err)
119 }