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"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
@ -24,11 +26,10 @@ type line struct {
|
||||||
UseExt bool
|
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")
|
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)
|
||||||
|
|
@ -65,12 +66,10 @@ func parseDefinitions(defFileName, outFileName, pkgName string) (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,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -119,12 +118,64 @@ 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.CatchTrace(1)
|
defer try.Catch(func(err try.Err) {
|
||||||
|
fmt.Println("Error: " + err.Error())
|
||||||
|
})
|
||||||
|
|
||||||
// Read flags
|
// Read flags
|
||||||
var definitionFile string
|
var definitionFile string
|
||||||
|
|
@ -135,16 +186,24 @@ 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) {
|
||||||
try.Check(try.NewErr("Definition file does not exist"))
|
fmt.Println("Definition file does not exist")
|
||||||
} else {
|
} else {
|
||||||
try.Check(
|
try.Check(parseDefinitions(definitionFile,
|
||||||
parseDefinitions(definitionFile, path.Join(targetDir, typeFile+".go"),
|
path.Join(targetDir, typeFile+".go"), *flagUseExt))
|
||||||
path.Base(targetDir)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
84
try/try.go
84
try/try.go
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,20 +42,12 @@ 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromErr wraps a standard error into a try-compatible one with extended stack info.
|
// newErr wraps a standard error into a try-compatible one with extended stack info.
|
||||||
func FromErr(err error) Err {
|
func newErr(err error) *tryErr {
|
||||||
// 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)
|
||||||
|
|
@ -82,15 +75,15 @@ func FromErr(err error) Err {
|
||||||
continue
|
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
|
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) Err {
|
func NewErr(msg string) *tryErr {
|
||||||
return FromErr(errors.New(msg))
|
return newErr(errors.New(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns the standard go error message (initial message + annotations).
|
// 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.
|
// 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.err.Error())
|
res := fmt.Sprintf("ERROR: %s\n", e.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.File, c.Line)
|
res += fmt.Sprintf("%s:%d\n", c.Function, c.Line)
|
||||||
res += fmt.Sprintf(" %s\n", c.Function)
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +150,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(FromErr(err))
|
panic(newErr(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +165,7 @@ func check(args []interface{}) {
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("wrong signature")
|
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
|
// 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.
|
||||||
|
|
@ -212,6 +203,9 @@ 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 {
|
||||||
|
|
@ -224,6 +218,9 @@ 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 {
|
||||||
|
|
@ -234,18 +231,20 @@ func CatchAll(errorHandler func(err Err), panicHandler func(v interface{})) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CatchTrace catches all errors and prints their call stack.
|
// CatchTrace is a helper function to catch and handle all errors. It recovers a
|
||||||
// Setting the exit parameter to a value above 0 will make the program
|
// panic as well and prints its call stack. This is preferred helper for go
|
||||||
// exit in case of an error.
|
// workers on long running servers.
|
||||||
func CatchTrace(exit int) {
|
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 {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if !ok {
|
if ok {
|
||||||
panic(r)
|
errorHandler(e)
|
||||||
}
|
} else {
|
||||||
e.PrintCallStack()
|
println(r)
|
||||||
if exit > 0 {
|
debug.PrintStack()
|
||||||
os.Exit(exit)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,6 +253,9 @@ func CatchTrace(exit int) {
|
||||||
// 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 {
|
||||||
|
|
@ -263,19 +265,13 @@ func Return(err *Err) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReturnStd is like Return, but it returns the Go standard error type.
|
// Annotate is for annotating an error. It's similar to Returnf but it takes only
|
||||||
func ReturnStd(err *error) {
|
// two arguments: a prefix string and a pointer to error. It adds ": " between
|
||||||
if r := recover(); r != nil {
|
// the prefix and the error text automatically.
|
||||||
e, ok := r.(Err)
|
|
||||||
if !ok {
|
|
||||||
panic(r) // Not ours, carry on panicking
|
|
||||||
}
|
|
||||||
*err = e.Unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Annotate adds additional messages to the error.
|
|
||||||
func Annotate(err *Err, msg string) {
|
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 {
|
if r := recover(); r != nil {
|
||||||
e, ok := r.(Err)
|
e, ok := r.(Err)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,21 @@ 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)
|
||||||
|
|
@ -164,9 +179,8 @@ func TestNewTryErr(t *testing.T) {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Count(callStack, "\n") != 9 {
|
if strings.Count(callStack, "\n") != 4 {
|
||||||
fmt.Println("Call stack is not 9 lines long.\n" +
|
fmt.Println("Call stack is not 4 lines long.\n" + callStack)
|
||||||
fmt.Sprint(strings.Count(callStack, "\n")))
|
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,43 +213,13 @@ func TestGetData(t *testing.T) {
|
||||||
|
|
||||||
func TestErrCompare(t *testing.T) {
|
func TestErrCompare(t *testing.T) {
|
||||||
err := errors.New("TestError")
|
err := errors.New("TestError")
|
||||||
tryErr := FromErr(err)
|
tryErr := newErr(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,6 +1,7 @@
|
||||||
package {{.PkgName}}
|
package try
|
||||||
|
|
||||||
// gotry auto-generated type definitions. DO NOT EDIT.
|
// gotry auto-generated type definitions. DO NOT EDIT.
|
||||||
|
{{- if .Imports }}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
{{- if .UseExt }}
|
{{- if .UseExt }}
|
||||||
|
|
@ -10,6 +11,9 @@ 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