github.com/haraldrudell/parl@v0.4.176/pruntime/code-location.go (about) 1 /* 2 © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package pruntime 7 8 import ( 9 "errors" 10 "fmt" 11 "path/filepath" 12 "runtime" 13 "strconv" 14 15 "github.com/haraldrudell/parl/pruntime/pruntimelib" 16 ) 17 18 const ( 19 // counts [pruntime.NewCodeLocation] 20 newCodeLocationStackFrames = 1 21 ) 22 23 // CodeLocation represents an executing code location, ie. a code line in source code 24 // - CodeLocation is similar to the location in [runtime.Frame], but 25 // contains only basic types string and int 26 type CodeLocation struct { 27 // File is absolute path to the Go source file 28 // - “/opt/foxyboy/sw/privates/parl/mains/executable.go” 29 // - may be “<autogenerated>” for tests 30 File string 31 // Line is the line number in the source file, eg. 35 32 Line int 33 // FuncName is the fully qualified Go package path, 34 // a possible value or pointer receiver struct name, 35 // and the function name 36 // - “github.com/haraldrudell/parl/mains.(*Executable).AddErr” 37 // - anonymous functions have names like “func1” 38 FuncName string 39 } 40 41 // NewCodeLocation gets data for a single stack frame 42 // - for stackFramesToSkip 0, NewCodeLocation returns data for 43 // its immediate caller 44 func NewCodeLocation(stackFramesToSkip int) (cl *CodeLocation) { 45 if stackFramesToSkip < 0 { 46 stackFramesToSkip = 0 47 } 48 var c = CodeLocation{} 49 50 // obtain code-location ifnrormation using [runtime.Caller] 51 var pc uintptr 52 var ok bool 53 // pc: opaque 54 // file: basename.go 55 // line: int 25 56 if pc, c.File, c.Line, ok = runtime.Caller(newCodeLocationStackFrames + stackFramesToSkip); !ok { 57 panic(errors.New("runtime.Caller failed")) 58 } 59 60 // obtain the fully qualified function name using [runtime.FuncForPC] 61 // rFunc: runtime.Func is all opaque fields. methods: 62 // Entry() (uintptr) 63 // FileLine(uintptr) (line string, line int) "/opt/foxyboy/sw/privates/parl/mains/executable.go" 64 // Name(): github.com/haraldrudell/parl/mains.(*Executable).AddErr 65 if rFunc := runtime.FuncForPC(pc); rFunc != nil { 66 c.FuncName = rFunc.Name() 67 } 68 69 return &c 70 } 71 72 // CodeLocationFromFunc returns a code location value from a 73 // non-nil [runtime.Func] value 74 // - Func values are return by [runtime.FuncForPC] 75 func CodeLocationFromFunc(runtimeFunc *runtime.Func) (cl *CodeLocation) { 76 cl = &CodeLocation{FuncName: runtimeFunc.Name()} 77 cl.File, cl.Line = runtimeFunc.FileLine(runtimeFunc.Entry()) 78 return 79 } 80 81 // FuncName returns function name, characters no space: 82 // “AddErr” 83 func (cl *CodeLocation) Name() (funcName string) { 84 _, _, _, funcName = pruntimelib.SplitAbsoluteFunctionName(cl.FuncName) 85 return 86 } 87 88 // Package return base package name, a single word of characters with no space: 89 // “mains” 90 func (cl *CodeLocation) Package() (packageName string) { 91 _, packageName, _, _ = pruntimelib.SplitAbsoluteFunctionName(cl.FuncName) 92 return 93 } 94 95 // PackFunc return base package name and function 96 // “mains.AddErr” 97 func (cl *CodeLocation) PackFunc() (packageDotFunction string) { 98 _, packageName, _, funcName := pruntimelib.SplitAbsoluteFunctionName(cl.FuncName) 99 return packageName + "." + funcName 100 } 101 102 // FuncIdentifier return the function name identifier 103 // “AddErr” 104 // - anonymous function like “SomeFunc.func1” 105 func (cl *CodeLocation) FuncIdentifier() (funcIdentifier string) { 106 _, _, _, funcIdentifier = pruntimelib.SplitAbsoluteFunctionName(cl.FuncName) 107 return 108 } 109 110 // Base returns base package name, an optional type name and the function name: 111 // “mains.(*Executable).AddErr” 112 func (cl *CodeLocation) Base() (baseName string) { 113 return filepath.Base(cl.FuncName) 114 } 115 116 // FuncLine retuns the fully qualified function name and its line number: 117 // “github.com/haraldrudell/parl/mains.(*Executable).AddErr:43” 118 func (cl *CodeLocation) FuncLine() (funcLine string) { 119 return cl.FuncName + ":" + strconv.Itoa(cl.Line) 120 } 121 122 // Short returns base package name, an optional type name and 123 // the function name, base filename and line number: 124 // “mains.(*Executable).AddErr-executable.go:25” 125 func (cl *CodeLocation) Short() (funcName string) { 126 return fmt.Sprintf("%s()-%s:%d", filepath.Base(cl.FuncName), filepath.Base(cl.File), cl.Line) 127 } 128 129 // Long returns full package path, an optional type name and 130 // the function name, base filename and line number: 131 // “github.com/haraldrudell/parl/mains.(*Executable).AddErr-executable.go:25” 132 func (cl *CodeLocation) Long() (funcName string) { 133 return fmt.Sprintf("%s-%s:%d", cl.FuncName, cl.File, cl.Line) 134 } 135 136 // IsSet returns true if this CodeLocation has a value, ie. is not zero-value 137 func (cl *CodeLocation) IsSet() (isSet bool) { return cl.File != "" || cl.FuncName != "" } 138 139 // Dump outputs all values quoted for debug purposes: 140 // - File: "/opt/homebrew/Cellar/go/1.20.4/libexec/src/testing/testing.go" 141 // - Line: 1576 142 // - FuncName: "github.com/haraldrudell/parl/mains.(*Executable).AddErr" 143 func (cl *CodeLocation) Dump() (s string) { 144 return fmt.Sprintf("File: %q Line: %d FuncName: %q", cl.File, cl.Line, cl.FuncName) 145 } 146 147 // String returns a two-line string representation suitable for a multi-line stack trace. 148 // Typical output: 149 // - “github.com/haraldrudell/parl/error116.(*TypeName).FuncName 150 // ␠␠/opt/sw/privates/parl/error116/codelocation_test.go:20” 151 // - indentation is two spaces, not tab characters 152 func (cl CodeLocation) String() string { 153 return fmt.Sprintf("%s\n\x20\x20%s:%d", cl.FuncName, cl.File, cl.Line) 154 }