6 Commits d52cdbd27f ... e454283931

Author SHA1 Message Date
  Adam e454283931 Merge branch 'v1.1.0' 4 years ago
  Adam 3a2a3bffad update changelog 4 years ago
  Adam 698c5262f3 add logging 4 years ago
  Adam Pioterek 3facb436ed make Exception’s value public 4 years ago
  Adam Pioterek f4a9aff676 add Recover method 4 years ago
  Adam Pioterek cc5beb2a69 Merge branch 'v1.0.1' into develop 4 years ago
3 changed files with 145 additions and 12 deletions
  1. 8 0
      CHANGELOG.rst
  2. 110 6
      gott.go
  3. 27 6
      gott_test.go

+ 8 - 0
CHANGELOG.rst

@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
 
 The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_`, using ReStructuredText instead of Markdown, and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
 
+[1.1.0] - 2020-02-11
+====================
+
+Added
+-----
+
+* Logging with 4 levels
+
 [1.0.1] - 2019-06-29
 ====================
 

+ 110 - 6
gott.go

@@ -1,11 +1,40 @@
 package gott
 
-import "fmt"
+import (
+	"fmt"
+	"log"
+	"reflect"
+	"runtime"
+)
+
+type LogLevel int
+
+// LogLevel specifies what to log:
+// Quiet logs nothing,
+// Error logs only errors,
+// Debug logs functions that run,
+// Info logs run and skipped functions
+const (
+	Quiet LogLevel = iota
+	Error
+	Debug
+	Info
+)
+
+func logErr(e error, fn interface{}) {
+	fnName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
+	log.Printf("Function %s returned error: %v", fnName, e)
+}
+
+func logMsg(msg string, fn interface{}) {
+	fnName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
+	log.Printf("%s %s", msg, fnName)
+}
 
 // Exception is a type encapsulating anything contained in panic.
 // It implements Error() and therefore can be used as error.
 type Exception struct {
-	e interface{}
+	E interface{}
 }
 
 func (e Exception) Error() string {
@@ -18,13 +47,14 @@ type Tuple []interface{}
 // Result is a simplification of Either monad. It’s either successful—and
 // carries an interface{}—or unsuccessful—and carries an error.
 type Result struct {
-	s interface{}
-	e error
+	s        interface{}
+	e        error
+	LogLevel LogLevel
 }
 
 // NewResult creates initial Result passed to functions.
 func NewResult(s interface{}) *Result {
-	return &Result{s, nil}
+	return &Result{s, nil, Quiet}
 }
 
 // Bind performs fn on the receiver’s success value and assigns the returned
@@ -33,11 +63,21 @@ func NewResult(s interface{}) *Result {
 // Bind operates on functions that return value and error.
 func (r *Result) Bind(fn func(...interface{}) (interface{}, error)) *Result {
 	if r.e == nil {
+		if r.LogLevel >= Debug {
+			logMsg("running:", fn)
+		}
 		if s, ok := r.s.(Tuple); ok {
 			r.s, r.e = fn(s...)
 		} else {
 			r.s, r.e = fn(r.s)
 		}
+		if r.e != nil && r.LogLevel >= Error {
+			logErr(r.e, fn)
+		}
+	} else {
+		if r.LogLevel >= Info {
+			logMsg("skipping:", fn)
+		}
 	}
 	return r
 }
@@ -49,11 +89,18 @@ func (r *Result) Bind(fn func(...interface{}) (interface{}, error)) *Result {
 // value
 func (r *Result) Map(fn func(...interface{}) interface{}) *Result {
 	if r.e == nil {
+		if r.LogLevel >= Debug {
+			logMsg("running:", fn)
+		}
 		if s, ok := r.s.(Tuple); ok {
 			r.s = fn(s...)
 		} else {
 			r.s = fn(r.s)
 		}
+	} else {
+		if r.LogLevel >= Info {
+			logMsg("skipping:", fn)
+		}
 	}
 	return r
 }
@@ -65,12 +112,21 @@ func (r *Result) Map(fn func(...interface{}) interface{}) *Result {
 // error
 func (r *Result) Tee(fn func(...interface{}) error) *Result {
 	if r.e == nil {
-		r.s = nil
+		if r.LogLevel >= Debug {
+			logMsg("running:", fn)
+		}
 		if s, ok := r.s.(Tuple); ok {
 			r.e = fn(s...)
 		} else {
 			r.e = fn(r.s)
 		}
+		if r.e != nil && r.LogLevel >= Error {
+			logErr(r.e, fn)
+		}
+	} else {
+		if r.LogLevel >= Info {
+			logMsg("skipping:", fn)
+		}
 	}
 	return r
 }
@@ -81,11 +137,18 @@ func (r *Result) Tee(fn func(...interface{}) error) *Result {
 // successful.
 func (r *Result) SafeTee(fn func(...interface{})) *Result {
 	if r.e == nil {
+		if r.LogLevel >= Debug {
+			logMsg("running:", fn)
+		}
 		if s, ok := r.s.(Tuple); ok {
 			fn(s...)
 		} else {
 			fn(r.s)
 		}
+	} else {
+		if r.LogLevel >= Info {
+			logMsg("skipping:", fn)
+		}
 	}
 	return r
 }
@@ -96,10 +159,16 @@ func (r *Result) SafeTee(fn func(...interface{})) *Result {
 // case, Catch returns the receiver.
 func (r *Result) Catch(fn func(...interface{}) interface{}) (result *Result) {
 	if r.e == nil {
+		if r.LogLevel >= Debug {
+			logMsg("running:", fn)
+		}
 		defer func() {
 			if err := recover(); err != nil {
 				r.e = Exception{err}
 				result = r
+				if r.e != nil && r.LogLevel >= Error {
+					logErr(r.e, fn)
+				}
 			}
 		}()
 		if s, ok := r.s.(Tuple); ok {
@@ -107,6 +176,35 @@ func (r *Result) Catch(fn func(...interface{}) interface{}) (result *Result) {
 		} else {
 			r.s = fn(r.s)
 		}
+	} else {
+		if r.LogLevel >= Info {
+			logMsg("skipping:", fn)
+		}
+	}
+	return r
+}
+
+// Revover tries to put processing back on the happy track.
+// If receiver is not successful, Recover calls the passed function and
+// assignes the returned values to the receiver. In either case, Recover
+// returns the receiver.
+func (r *Result) Recover(fn func(...interface{}) (interface{}, error)) *Result {
+	if r.e != nil {
+		if r.LogLevel >= Debug {
+			logMsg("running:", fn)
+		}
+		if s, ok := r.s.(Tuple); ok {
+			r.s, r.e = fn(s...)
+		} else {
+			r.s, r.e = fn(r.s)
+		}
+		if r.e != nil && r.LogLevel >= Error {
+			logErr(r.e, fn)
+		}
+	} else {
+		if r.LogLevel >= Info {
+			logMsg("skipping:", fn)
+		}
 	}
 	return r
 }
@@ -116,12 +214,18 @@ func (r *Result) Catch(fn func(...interface{}) interface{}) (result *Result) {
 // Handle returns the receiver.
 func (r *Result) Handle(onSuccess func(...interface{}), onError func(error)) *Result {
 	if r.e == nil {
+		if r.LogLevel >= Debug {
+			logMsg("running:", onSuccess)
+		}
 		if s, ok := r.s.(Tuple); ok {
 			onSuccess(s...)
 		} else {
 			onSuccess(r.s)
 		}
 	} else {
+		if r.LogLevel >= Debug {
+			logMsg("running:", onError)
+		}
 		onError(r.e)
 	}
 	return r

+ 27 - 6
gott_test.go

@@ -24,7 +24,9 @@ func TestBindOk(t *testing.T) {
 }
 
 func TestBindErr(t *testing.T) {
-	_, e := NewResult(0).Bind(divide5).Finish()
+	r := NewResult(0)
+	r.LogLevel = Error
+	_, e := r.Bind(divide5).Finish()
 	if e == nil {
 		t.Errorf("Error not returned\n")
 	}
@@ -53,7 +55,9 @@ func sideEffect(...interface{}) {
 }
 
 func TestTee(t *testing.T) {
-	_, e := NewResult(0).Tee(sideEffectWithError).Finish()
+	r := NewResult(0)
+	r.LogLevel = Error
+	_, e := r.Tee(sideEffectWithError).Finish()
 	if e == nil {
 		t.Errorf("Error not returned\n")
 	}
@@ -97,8 +101,10 @@ func histeric(...interface{}) interface{} {
 }
 
 func TestCatch(t *testing.T) {
-	_, e := NewResult(0).Catch(histeric).Finish()
-	if e.(Exception).e != 42 {
+	r := NewResult(0)
+	r.LogLevel = Error
+	_, e := r.Catch(histeric).Finish()
+	if e.(Exception).E != 42 {
 		t.Errorf("%s, want 42\n", e)
 	}
 }
@@ -113,7 +119,9 @@ func TestFinishOK(t *testing.T) {
 }
 
 func TestFinishErr(t *testing.T) {
-	_, e := NewResult(0).Bind(divide5).Finish()
+	r := NewResult(0)
+	r.LogLevel = Error
+	_, e := r.Bind(divide5).Finish()
 	if e == nil {
 		t.Errorf("Error not returned\n")
 	}
@@ -129,8 +137,21 @@ func TestTuple(t *testing.T) {
 	}
 	s, e := NewResult(Tuple(x[:])).Bind(join).Finish()
 	if e != nil {
-		t.Errorf("Error returned")
+		t.Errorf("Error returned\n")
 	} else if s != "onetwo" {
 		t.Errorf("one + two = %s; want onetwo\n", x)
 	}
 }
+
+func setZero(values ...interface{}) (interface{}, error) {
+	return 0, nil
+}
+
+func TestRecover(t *testing.T) {
+	s, e := NewResult(0).Bind(divide5).Recover(setZero).Finish()
+	if e != nil {
+		t.Errorf("Error returned\n")
+	} else if s != 0 {
+		t.Errorf("set %d; want 0\n", s)
+	}
+}