Compare commits
3 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34a01c6279 | |||
| b6ff49baee | |||
| 1cf7829749 |
4 changed files with 88 additions and 131 deletions
|
|
@ -4,11 +4,9 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
@ -26,10 +24,11 @@ type line struct {
|
||||||
UseExt bool
|
UseExt bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDefinitions(defFileName, outFileName string, useExt bool) (err try.Err) {
|
func parseDefinitions(defFileName, outFileName, pkgName string) (err try.Err) {
|
||||||
defer try.Annotate(&err, "error parsing definitions")
|
defer try.Annotate(&err, "error parsing definitions")
|
||||||
|
|
||||||
defFile := try.File(os.Open(defFileName))
|
defFile := try.File(os.Open(defFileName))
|
||||||
|
useExt := pkgName != "try"
|
||||||
|
|
||||||
fileScanner := bufio.NewScanner(defFile)
|
fileScanner := bufio.NewScanner(defFile)
|
||||||
fileScanner.Split(bufio.ScanLines)
|
fileScanner.Split(bufio.ScanLines)
|
||||||
|
|
@ -66,10 +65,12 @@ func parseDefinitions(defFileName, outFileName string, useExt bool) (err try.Err
|
||||||
try.Check(tmpl.Execute(outFile, struct {
|
try.Check(tmpl.Execute(outFile, struct {
|
||||||
Definitions []line
|
Definitions []line
|
||||||
Imports map[string]bool
|
Imports map[string]bool
|
||||||
|
PkgName string
|
||||||
UseExt bool
|
UseExt bool
|
||||||
}{
|
}{
|
||||||
Definitions: lines,
|
Definitions: lines,
|
||||||
Imports: imports,
|
Imports: imports,
|
||||||
|
PkgName: pkgName,
|
||||||
UseExt: useExt,
|
UseExt: useExt,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -118,64 +119,12 @@ func parseLine(lineStr string, imports map[string]bool) (*line, try.Err) {
|
||||||
return &lineData, nil
|
return &lineData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyLibrary(targetDir string, noTests, noStds bool) (caught try.Err) {
|
|
||||||
defer try.Return(&caught)
|
|
||||||
|
|
||||||
filesToCopy := []string{"try.go"}
|
|
||||||
|
|
||||||
if !noTests {
|
|
||||||
filesToCopy = append(filesToCopy, "try_test.go")
|
|
||||||
}
|
|
||||||
if !noStds {
|
|
||||||
filesToCopy = append(filesToCopy, "std_types.go")
|
|
||||||
if !noTests {
|
|
||||||
filesToCopy = append(filesToCopy, "std_types_test.go")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fname := range filesToCopy {
|
|
||||||
f, e := gotry.TryFiles.Open("try/" + fname)
|
|
||||||
try.Check(e)
|
|
||||||
|
|
||||||
targetPath := filepath.Join(targetDir, fname)
|
|
||||||
dest := try.File(os.Create(targetPath))
|
|
||||||
|
|
||||||
try.Empty(io.Copy(dest, f))
|
|
||||||
fmt.Printf("Copied %s\n", fname)
|
|
||||||
|
|
||||||
try.Check(f.Close())
|
|
||||||
try.Check(dest.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add go-generate command
|
|
||||||
gencmd := "\n//go:generate go run code.thetadev.de/ThetaDev/gotry/gotry_generate " +
|
|
||||||
"-def types.csv -o .\n"
|
|
||||||
f := try.File(
|
|
||||||
os.OpenFile(filepath.Join(targetDir, "try.go"), os.O_APPEND|os.O_WRONLY, 0o644))
|
|
||||||
try.Empty(f.WriteString(gencmd))
|
|
||||||
try.Check(f.Close())
|
|
||||||
|
|
||||||
// If not present, add definition file template
|
|
||||||
definitionFile := filepath.Join(targetDir, "types.csv")
|
|
||||||
|
|
||||||
if _, e := os.Stat(definitionFile); os.IsNotExist(e) {
|
|
||||||
f := try.File(os.Create(definitionFile))
|
|
||||||
try.Int(f.WriteString(
|
|
||||||
"# Add your type definitions here\n# Example:\n# DB;*gorm.DB;gorm.io/gorm\n"))
|
|
||||||
try.Check(f.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func goFmt(goFile string) error {
|
func goFmt(goFile string) error {
|
||||||
return exec.Command("gofmt", "-s", "-w", goFile).Run()
|
return exec.Command("gofmt", "-s", "-w", goFile).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
defer try.Catch(func(err try.Err) {
|
defer try.CatchTrace(1)
|
||||||
fmt.Println("Error: " + err.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
// Read flags
|
// Read flags
|
||||||
var definitionFile string
|
var definitionFile string
|
||||||
|
|
@ -186,24 +135,16 @@ func main() {
|
||||||
"Definition file")
|
"Definition file")
|
||||||
flag.StringVar(&targetDir, "o", "try", "Target directory")
|
flag.StringVar(&targetDir, "o", "try", "Target directory")
|
||||||
flag.StringVar(&typeFile, "of", "types", "Name of generated type definition file")
|
flag.StringVar(&typeFile, "of", "types", "Name of generated type definition file")
|
||||||
flagInit := flag.Bool("init", false, "Copy gotry package")
|
|
||||||
flagNoTests := flag.Bool("no-tests", false, "Dont copy tests")
|
|
||||||
flagNoStds := flag.Bool("no-std", false, "Dont copy std types")
|
|
||||||
flagUseExt := flag.Bool("ext", false, "Use gotry as an external dependency")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
try.Check(os.MkdirAll(targetDir, 0o777))
|
try.Check(os.MkdirAll(targetDir, 0o777))
|
||||||
|
|
||||||
// Copy library
|
|
||||||
if *flagInit {
|
|
||||||
try.Check(copyLibrary(targetDir, *flagNoTests, *flagNoStds))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate types
|
// Generate types
|
||||||
if _, e := os.Stat(definitionFile); os.IsNotExist(e) {
|
if _, e := os.Stat(definitionFile); os.IsNotExist(e) {
|
||||||
fmt.Println("Definition file does not exist")
|
try.Check(try.NewErr("Definition file does not exist"))
|
||||||
} else {
|
} else {
|
||||||
try.Check(parseDefinitions(definitionFile,
|
try.Check(
|
||||||
path.Join(targetDir, typeFile+".go"), *flagUseExt))
|
parseDefinitions(definitionFile, path.Join(targetDir, typeFile+".go"),
|
||||||
|
path.Base(targetDir)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
84
try/try.go
84
try/try.go
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -42,12 +41,20 @@ type tryErrData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type call struct {
|
type call struct {
|
||||||
|
File string `json:"file"`
|
||||||
Function string `json:"fn"`
|
Function string `json:"fn"`
|
||||||
Line int `json:"l"`
|
Line int `json:"l"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// newErr wraps a standard error into a try-compatible one with extended stack info.
|
// FromErr wraps a standard error into a try-compatible one with extended stack info.
|
||||||
func newErr(err error) *tryErr {
|
func FromErr(err error) Err {
|
||||||
|
// If the error is already try-compatible, return
|
||||||
|
//nolint:errorlint
|
||||||
|
cterr, ok := err.(Err)
|
||||||
|
if ok {
|
||||||
|
return cterr
|
||||||
|
}
|
||||||
|
|
||||||
terr := &tryErr{err: err, callStack: []call{}}
|
terr := &tryErr{err: err, callStack: []call{}}
|
||||||
|
|
||||||
pc, _, _, ok := runtime.Caller(0)
|
pc, _, _, ok := runtime.Caller(0)
|
||||||
|
|
@ -75,15 +82,15 @@ func newErr(err error) *tryErr {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
terr.callStack = append(terr.callStack, call{Line: l, Function: fnc})
|
terr.callStack = append(terr.callStack, call{File: f, Function: fnc, Line: l})
|
||||||
}
|
}
|
||||||
|
|
||||||
return terr
|
return terr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewErr creates a new try-compatible error with extended stack info.
|
// NewErr creates a new try-compatible error with extended stack info.
|
||||||
func NewErr(msg string) *tryErr {
|
func NewErr(msg string) Err {
|
||||||
return newErr(errors.New(msg))
|
return FromErr(errors.New(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns the standard go error message (initial message + annotations).
|
// Error returns the standard go error message (initial message + annotations).
|
||||||
|
|
@ -106,9 +113,14 @@ func (e *tryErr) CallStack() []call {
|
||||||
|
|
||||||
// CallStackString outputs the call stack as a printable multiline string.
|
// CallStackString outputs the call stack as a printable multiline string.
|
||||||
func (e *tryErr) CallStackString() string {
|
func (e *tryErr) CallStackString() string {
|
||||||
res := fmt.Sprintf("ERROR: %s\n", e.Error())
|
res := fmt.Sprintf("ERROR: %s\n", e.err.Error())
|
||||||
|
for _, msg := range e.annotations {
|
||||||
|
res += fmt.Sprintln(msg)
|
||||||
|
}
|
||||||
|
res += fmt.Sprintln("\nTraceback (most recent call first):")
|
||||||
for _, c := range e.callStack {
|
for _, c := range e.callStack {
|
||||||
res += fmt.Sprintf("%s:%d\n", c.Function, c.Line)
|
res += fmt.Sprintf("%s:%d\n", c.File, c.Line)
|
||||||
|
res += fmt.Sprintf(" %s\n", c.Function)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +162,7 @@ func Any(args ...interface{}) []interface{} {
|
||||||
// {return err} on happy path.
|
// {return err} on happy path.
|
||||||
func Check(err error) {
|
func Check(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(newErr(err))
|
panic(FromErr(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +177,7 @@ func check(args []interface{}) {
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("wrong signature")
|
panic("wrong signature")
|
||||||
}
|
}
|
||||||
panic(newErr(err))
|
panic(FromErr(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,9 +186,6 @@ func check(args []interface{}) {
|
||||||
// return errors there is a Catch function. Note! The handler function f is
|
// return errors there is a Catch function. Note! The handler function f is
|
||||||
// called only when err != nil.
|
// called only when err != nil.
|
||||||
func Handle(err *Err, f func()) {
|
func Handle(err *Err, f func()) {
|
||||||
// This and Catch are similar but we need to call recover() here because
|
|
||||||
// how it works with defer. We cannot refactor these to use same function.
|
|
||||||
|
|
||||||
// We put real panic objects back and keep only those which are
|
// We put real panic objects back and keep only those which are
|
||||||
// carrying our errors. We must also call all of the handlers in defer
|
// carrying our errors. We must also call all of the handlers in defer
|
||||||
// stack.
|
// stack.
|
||||||
|
|
@ -203,9 +212,6 @@ func Handle(err *Err, f func()) {
|
||||||
// Catch function per non error returning function. See Handle for more
|
// Catch function per non error returning function. See Handle for more
|
||||||
// information.
|
// information.
|
||||||
func Catch(f func(err Err)) {
|
func Catch(f func(err Err)) {
|
||||||
// This and Handle are similar but we need to call recover here because how
|
|
||||||
// it works with defer. We cannot refactor these 2 to use same function.
|
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -218,9 +224,6 @@ func Catch(f func(err Err)) {
|
||||||
// CatchAll is a helper function to catch and write handlers for all errors and
|
// CatchAll is a helper function to catch and write handlers for all errors and
|
||||||
// all panics thrown in the current go routine.
|
// all panics thrown in the current go routine.
|
||||||
func CatchAll(errorHandler func(err Err), panicHandler func(v interface{})) {
|
func CatchAll(errorHandler func(err Err), panicHandler func(v interface{})) {
|
||||||
// This and Handle are similar but we need to call recover here because how
|
|
||||||
// it works with defer. We cannot refactor these 2 to use same function.
|
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
@ -231,20 +234,18 @@ func CatchAll(errorHandler func(err Err), panicHandler func(v interface{})) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CatchTrace is a helper function to catch and handle all errors. It recovers a
|
// CatchTrace catches all errors and prints their call stack.
|
||||||
// panic as well and prints its call stack. This is preferred helper for go
|
// Setting the exit parameter to a value above 0 will make the program
|
||||||
// workers on long running servers.
|
// exit in case of an error.
|
||||||
func CatchTrace(errorHandler func(err Err)) {
|
func CatchTrace(exit int) {
|
||||||
// This and Handle are similar but we need to call recover here because how
|
|
||||||
// it works with defer. We cannot refactor these 2 to use same function.
|
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if ok {
|
if !ok {
|
||||||
errorHandler(e)
|
panic(r)
|
||||||
} else {
|
}
|
||||||
println(r)
|
e.PrintCallStack()
|
||||||
debug.PrintStack()
|
if exit > 0 {
|
||||||
|
os.Exit(exit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,9 +254,6 @@ func CatchTrace(errorHandler func(err Err)) {
|
||||||
// their errors. If you want to annotate errors see Annotate for more
|
// their errors. If you want to annotate errors see Annotate for more
|
||||||
// information.
|
// information.
|
||||||
func Return(err *Err) {
|
func Return(err *Err) {
|
||||||
// This and Handle are similar but we need to call recover here because how
|
|
||||||
// it works with defer. We cannot refactor these two to use same function.
|
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -265,13 +263,19 @@ func Return(err *Err) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Annotate is for annotating an error. It's similar to Returnf but it takes only
|
// ReturnStd is like Return, but it returns the Go standard error type.
|
||||||
// two arguments: a prefix string and a pointer to error. It adds ": " between
|
func ReturnStd(err *error) {
|
||||||
// the prefix and the error text automatically.
|
if r := recover(); r != nil {
|
||||||
func Annotate(err *Err, msg string) {
|
e, ok := r.(Err)
|
||||||
// This and Handle are similar but we need to call recover here because how
|
if !ok {
|
||||||
// it works with defer. We cannot refactor these two to use same function.
|
panic(r) // Not ours, carry on panicking
|
||||||
|
}
|
||||||
|
*err = e.Unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotate adds additional messages to the error.
|
||||||
|
func Annotate(err *Err, msg string) {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
||||||
|
|
@ -114,21 +114,6 @@ func TestPanickingCatchAll(t *testing.T) {
|
||||||
panickingCatchAll()
|
panickingCatchAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func panickingCatchTrace() {
|
|
||||||
defer CatchTrace(func(err Err) {})
|
|
||||||
|
|
||||||
Any(wrongSignature())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPanickingCatchTrace(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
t.Error("panics should NOT carry on when tracing")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
panickingCatchTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
func panickingReturn() {
|
func panickingReturn() {
|
||||||
var err Err
|
var err Err
|
||||||
defer Return(&err)
|
defer Return(&err)
|
||||||
|
|
@ -179,8 +164,9 @@ func TestNewTryErr(t *testing.T) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Count(callStack, "\n") != 4 {
|
if strings.Count(callStack, "\n") != 9 {
|
||||||
fmt.Println("Call stack is not 4 lines long.\n" + callStack)
|
fmt.Println("Call stack is not 9 lines long.\n" +
|
||||||
|
fmt.Sprint(strings.Count(callStack, "\n")))
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,13 +199,43 @@ func TestGetData(t *testing.T) {
|
||||||
|
|
||||||
func TestErrCompare(t *testing.T) {
|
func TestErrCompare(t *testing.T) {
|
||||||
err := errors.New("TestError")
|
err := errors.New("TestError")
|
||||||
tryErr := newErr(err)
|
tryErr := FromErr(err)
|
||||||
|
|
||||||
if !errors.Is(tryErr, err) {
|
if !errors.Is(tryErr, err) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReturnStd(t *testing.T) {
|
||||||
|
tf := func() (err error) {
|
||||||
|
defer ReturnStd(&err)
|
||||||
|
String(throw())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tf()
|
||||||
|
|
||||||
|
if err.Error() != "this is an ERROR" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckTryErr(t *testing.T) {
|
||||||
|
testErr := NewErr("TestErr")
|
||||||
|
|
||||||
|
tf := func() (err Err) {
|
||||||
|
defer Return(&err)
|
||||||
|
Check(testErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tf()
|
||||||
|
|
||||||
|
if err != testErr {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleReturn() {
|
func ExampleReturn() {
|
||||||
var err Err
|
var err Err
|
||||||
defer Return(&err)
|
defer Return(&err)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package try
|
package {{.PkgName}}
|
||||||
|
|
||||||
// gotry auto-generated type definitions. DO NOT EDIT.
|
// gotry auto-generated type definitions. DO NOT EDIT.
|
||||||
{{- if .Imports }}
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
{{- if .UseExt }}
|
{{- if .UseExt }}
|
||||||
|
|
@ -11,9 +10,6 @@ import (
|
||||||
"{{$pkg}}"
|
"{{$pkg}}"
|
||||||
{{- end}}
|
{{- end}}
|
||||||
)
|
)
|
||||||
{{- else if .UseExt }}
|
|
||||||
import "code.thetadev.de/ThetaDev/gotry/try"
|
|
||||||
{{- end}}
|
|
||||||
{{range $d := .Definitions }}
|
{{range $d := .Definitions }}
|
||||||
// {{$d.Name}} is a helper method to handle errors of
|
// {{$d.Name}} is a helper method to handle errors of
|
||||||
// func() ({{range $i, $t := $d.Types}}{{$t}}, {{end}}error) functions.
|
// func() ({{range $i, $t := $d.Types}}{{$t}}, {{end}}error) functions.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue