Compare commits
No commits in common. "main" and "v0.2.0" have entirely different histories.
4 changed files with 131 additions and 88 deletions
|
|
@ -4,9 +4,11 @@ import (
|
|||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
|
@ -24,11 +26,10 @@ type line struct {
|
|||
UseExt bool
|
||||
}
|
||||
|
||||
func parseDefinitions(defFileName, outFileName, pkgName string) (err try.Err) {
|
||||
func parseDefinitions(defFileName, outFileName string, useExt bool) (err try.Err) {
|
||||
defer try.Annotate(&err, "error parsing definitions")
|
||||
|
||||
defFile := try.File(os.Open(defFileName))
|
||||
useExt := pkgName != "try"
|
||||
|
||||
fileScanner := bufio.NewScanner(defFile)
|
||||
fileScanner.Split(bufio.ScanLines)
|
||||
|
|
@ -65,12 +66,10 @@ func parseDefinitions(defFileName, outFileName, pkgName string) (err try.Err) {
|
|||
try.Check(tmpl.Execute(outFile, struct {
|
||||
Definitions []line
|
||||
Imports map[string]bool
|
||||
PkgName string
|
||||
UseExt bool
|
||||
}{
|
||||
Definitions: lines,
|
||||
Imports: imports,
|
||||
PkgName: pkgName,
|
||||
UseExt: useExt,
|
||||
}))
|
||||
|
||||
|
|
@ -119,12 +118,64 @@ func parseLine(lineStr string, imports map[string]bool) (*line, try.Err) {
|
|||
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 {
|
||||
return exec.Command("gofmt", "-s", "-w", goFile).Run()
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer try.CatchTrace(1)
|
||||
defer try.Catch(func(err try.Err) {
|
||||
fmt.Println("Error: " + err.Error())
|
||||
})
|
||||
|
||||
// Read flags
|
||||
var definitionFile string
|
||||
|
|
@ -135,16 +186,24 @@ func main() {
|
|||
"Definition file")
|
||||
flag.StringVar(&targetDir, "o", "try", "Target directory")
|
||||
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()
|
||||
|
||||
try.Check(os.MkdirAll(targetDir, 0o777))
|
||||
|
||||
// Copy library
|
||||
if *flagInit {
|
||||
try.Check(copyLibrary(targetDir, *flagNoTests, *flagNoStds))
|
||||
}
|
||||
|
||||
// Generate types
|
||||
if _, e := os.Stat(definitionFile); os.IsNotExist(e) {
|
||||
try.Check(try.NewErr("Definition file does not exist"))
|
||||
fmt.Println("Definition file does not exist")
|
||||
} else {
|
||||
try.Check(
|
||||
parseDefinitions(definitionFile, path.Join(targetDir, typeFile+".go"),
|
||||
path.Base(targetDir)))
|
||||
try.Check(parseDefinitions(definitionFile,
|
||||
path.Join(targetDir, typeFile+".go"), *flagUseExt))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
84
try/try.go
84
try/try.go
|
|
@ -12,6 +12,7 @@ import (
|
|||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -41,20 +42,12 @@ type tryErrData struct {
|
|||
}
|
||||
|
||||
type call struct {
|
||||
File string `json:"file"`
|
||||
Function string `json:"fn"`
|
||||
Line int `json:"l"`
|
||||
}
|
||||
|
||||
// FromErr wraps a standard error into a try-compatible one with extended stack info.
|
||||
func FromErr(err error) Err {
|
||||
// If the error is already try-compatible, return
|
||||
//nolint:errorlint
|
||||
cterr, ok := err.(Err)
|
||||
if ok {
|
||||
return cterr
|
||||
}
|
||||
|
||||
// newErr wraps a standard error into a try-compatible one with extended stack info.
|
||||
func newErr(err error) *tryErr {
|
||||
terr := &tryErr{err: err, callStack: []call{}}
|
||||
|
||||
pc, _, _, ok := runtime.Caller(0)
|
||||
|
|
@ -82,15 +75,15 @@ func FromErr(err error) Err {
|
|||
continue
|
||||
}
|
||||
|
||||
terr.callStack = append(terr.callStack, call{File: f, Function: fnc, Line: l})
|
||||
terr.callStack = append(terr.callStack, call{Line: l, Function: fnc})
|
||||
}
|
||||
|
||||
return terr
|
||||
}
|
||||
|
||||
// NewErr creates a new try-compatible error with extended stack info.
|
||||
func NewErr(msg string) Err {
|
||||
return FromErr(errors.New(msg))
|
||||
func NewErr(msg string) *tryErr {
|
||||
return newErr(errors.New(msg))
|
||||
}
|
||||
|
||||
// Error returns the standard go error message (initial message + annotations).
|
||||
|
|
@ -113,14 +106,9 @@ func (e *tryErr) CallStack() []call {
|
|||
|
||||
// CallStackString outputs the call stack as a printable multiline string.
|
||||
func (e *tryErr) CallStackString() string {
|
||||
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):")
|
||||
res := fmt.Sprintf("ERROR: %s\n", e.Error())
|
||||
for _, c := range e.callStack {
|
||||
res += fmt.Sprintf("%s:%d\n", c.File, c.Line)
|
||||
res += fmt.Sprintf(" %s\n", c.Function)
|
||||
res += fmt.Sprintf("%s:%d\n", c.Function, c.Line)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
@ -162,7 +150,7 @@ func Any(args ...interface{}) []interface{} {
|
|||
// {return err} on happy path.
|
||||
func Check(err error) {
|
||||
if err != nil {
|
||||
panic(FromErr(err))
|
||||
panic(newErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +165,7 @@ func check(args []interface{}) {
|
|||
if !ok {
|
||||
panic("wrong signature")
|
||||
}
|
||||
panic(FromErr(err))
|
||||
panic(newErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +174,9 @@ func check(args []interface{}) {
|
|||
// return errors there is a Catch function. Note! The handler function f is
|
||||
// called only when err != nil.
|
||||
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
|
||||
// carrying our errors. We must also call all of the handlers in defer
|
||||
// stack.
|
||||
|
|
@ -212,6 +203,9 @@ func Handle(err *Err, f func()) {
|
|||
// Catch function per non error returning function. See Handle for more
|
||||
// information.
|
||||
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 {
|
||||
e, ok := r.(Err)
|
||||
if !ok {
|
||||
|
|
@ -224,6 +218,9 @@ func Catch(f func(err Err)) {
|
|||
// CatchAll is a helper function to catch and write handlers for all errors and
|
||||
// all panics thrown in the current go routine.
|
||||
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 {
|
||||
e, ok := r.(Err)
|
||||
if ok {
|
||||
|
|
@ -234,18 +231,20 @@ func CatchAll(errorHandler func(err Err), panicHandler func(v interface{})) {
|
|||
}
|
||||
}
|
||||
|
||||
// CatchTrace catches all errors and prints their call stack.
|
||||
// Setting the exit parameter to a value above 0 will make the program
|
||||
// exit in case of an error.
|
||||
func CatchTrace(exit int) {
|
||||
// CatchTrace is a helper function to catch and handle all errors. It recovers a
|
||||
// panic as well and prints its call stack. This is preferred helper for go
|
||||
// workers on long running servers.
|
||||
func CatchTrace(errorHandler 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 {
|
||||
e, ok := r.(Err)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
e.PrintCallStack()
|
||||
if exit > 0 {
|
||||
os.Exit(exit)
|
||||
if ok {
|
||||
errorHandler(e)
|
||||
} else {
|
||||
println(r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -254,6 +253,9 @@ func CatchTrace(exit int) {
|
|||
// their errors. If you want to annotate errors see Annotate for more
|
||||
// information.
|
||||
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 {
|
||||
e, ok := r.(Err)
|
||||
if !ok {
|
||||
|
|
@ -263,19 +265,13 @@ func Return(err *Err) {
|
|||
}
|
||||
}
|
||||
|
||||
// ReturnStd is like Return, but it returns the Go standard error type.
|
||||
func ReturnStd(err *error) {
|
||||
if r := recover(); r != nil {
|
||||
e, ok := r.(Err)
|
||||
if !ok {
|
||||
panic(r) // Not ours, carry on panicking
|
||||
}
|
||||
*err = e.Unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Annotate adds additional messages to the error.
|
||||
// Annotate is for annotating an error. It's similar to Returnf but it takes only
|
||||
// two arguments: a prefix string and a pointer to error. It adds ": " between
|
||||
// the prefix and the error text automatically.
|
||||
func Annotate(err *Err, msg string) {
|
||||
// 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 {
|
||||
e, ok := r.(Err)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,21 @@ func TestPanickingCatchAll(t *testing.T) {
|
|||
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() {
|
||||
var err Err
|
||||
defer Return(&err)
|
||||
|
|
@ -164,9 +179,8 @@ func TestNewTryErr(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
|
||||
if strings.Count(callStack, "\n") != 9 {
|
||||
fmt.Println("Call stack is not 9 lines long.\n" +
|
||||
fmt.Sprint(strings.Count(callStack, "\n")))
|
||||
if strings.Count(callStack, "\n") != 4 {
|
||||
fmt.Println("Call stack is not 4 lines long.\n" + callStack)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
@ -199,43 +213,13 @@ func TestGetData(t *testing.T) {
|
|||
|
||||
func TestErrCompare(t *testing.T) {
|
||||
err := errors.New("TestError")
|
||||
tryErr := FromErr(err)
|
||||
tryErr := newErr(err)
|
||||
|
||||
if !errors.Is(tryErr, err) {
|
||||
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() {
|
||||
var err Err
|
||||
defer Return(&err)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package {{.PkgName}}
|
||||
package try
|
||||
|
||||
// gotry auto-generated type definitions. DO NOT EDIT.
|
||||
{{- if .Imports }}
|
||||
|
||||
import (
|
||||
{{- if .UseExt }}
|
||||
|
|
@ -10,6 +11,9 @@ import (
|
|||
"{{$pkg}}"
|
||||
{{- end}}
|
||||
)
|
||||
{{- else if .UseExt }}
|
||||
import "code.thetadev.de/ThetaDev/gotry/try"
|
||||
{{- end}}
|
||||
{{range $d := .Definitions }}
|
||||
// {{$d.Name}} is a helper method to handle errors of
|
||||
// func() ({{range $i, $t := $d.Types}}{{$t}}, {{end}}error) functions.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue