Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions encoding/gpkg/binary_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,20 +332,32 @@ func (sb StandardBinary) Encode() ([]byte, error) {
}

func NewBinary(srs int32, geo geom.Geometry) (*StandardBinary, error) {
extent, err := geom.NewExtentFromGeometry(geo)
if err != nil {
return nil, err

var (
emptyGeo = geom.IsEmpty(geo)
err error
extent = []float64{nan, nan, nan, nan}
h *BinaryHeader
)

if !emptyGeo {
ext, err := geom.NewExtentFromGeometry(geo)
if err != nil {
return nil, err
}
extent = ext[:]
}
h, err := NewBinaryHeader(binary.LittleEndian, srs, extent[:], EnvelopeTypeXY, false, false)

h, err = NewBinaryHeader(binary.LittleEndian, srs, extent, EnvelopeTypeXY, false, emptyGeo)
if err != nil {
return nil, err
}

return &StandardBinary{
Header: h,
SRSID: srs,
Geometry: geo,
}, nil

}

func (sb *StandardBinary) Extent() *geom.Extent {
Expand Down
62 changes: 62 additions & 0 deletions internal/debugger/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package debugger

import (
"fmt"
"strings"
"unicode/utf8"
)

const (

// CategoryGot is for got values of a testcase
CategoryGot = "got"
// CategoryExpected is the expected values of a testcase
CategoryExpected = "expected"
// CategoryInput is the input values of a testcase
CategoryInput = "input"
)

// CategoryFormatter is a helper category that will build a category
// made up of a set of values given with the With function.
// The category should be a template of where the With values will go.
// For example CategoryFormatter("%v_triangle_%v").With(1,10) will result
// in a string of `1_triangle_10` this can be used to create common
// category names.
type CategoryFormatter string

func (f CategoryFormatter) With(data ...interface{}) string { return fmt.Sprintf(string(f), data...) }
func (f CategoryFormatter) String() string { return string(f) }

// CategoryJoiner is a helper category that will build a category
// made up of a set of values given with the With function seperated
// the the last character of the category.
// For example CategoryJoiner("triangle:").With(1,10) will result
// in a string of `triangle:1:10` this can be used to create common
// category names.
type CategoryJoiner string

func (f CategoryJoiner) With(data ...interface{}) string {

var (
s strings.Builder
addc bool
join string
)

if len(string(f)) > 0 {
r, _ := utf8.DecodeLastRune([]byte(f))
join = string([]rune{r})
}

s.WriteString(string(f))
for _, v := range data {
if addc {
s.WriteString(join)
}
fmt.Fprintf(&s, "%v", v)
addc = true
}
return s.String()
}

func (f CategoryJoiner) String() string { return string(f) }
12 changes: 12 additions & 0 deletions internal/debugger/create_recorder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build !cgo

package debugger

import (
"github.com/gdey/errors"
rcdr "github.com/go-spatial/geom/internal/debugger/recorder"
)

func NewRecorder(_, _ string) (rcdr.Interface, string, error) {
return nil, "", errors.String("only supported in cgo")
}
18 changes: 18 additions & 0 deletions internal/debugger/create_recorder_cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// +build cgo

package debugger

import (
"fmt"

rcdr "github.com/go-spatial/geom/internal/debugger/recorder"
"github.com/go-spatial/geom/internal/debugger/recorder/gpkg"
)

func NewRecorder(dir, filename string) (rcdr.Interface, string, error) {
r, fn, err := gpkg.New(dir, filename, 0)
if err != nil {
return nil, fn, fmt.Errorf("gpkg error: %v", err)
}
return r, fn, nil
}
200 changes: 200 additions & 0 deletions internal/debugger/debugger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package debugger

import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
"unicode"

"github.com/pborman/uuid"
)

const (
// ContextRecorderKey the key to store the recorder in the context
ContextRecorderKey = "debugger_recorder_key"

// ContextRecorderKey the key to store the testname in the context
ContextRecorderTestnameKey = "debugger_recorder_testname_key"
)

// DefaultOutputDir is where the system will write the debugging db/files
// By default this will use os.TempDir() to write to the system temp directory
// set this in an init function to wite elsewhere.
var DefaultOutputDir = os.TempDir()

// AsString will create string contains the stringified items seperated by a ':'
func AsString(vs ...interface{}) string {
var s strings.Builder
var addc bool
for _, v := range vs {
if addc {
s.WriteString(":")
}
fmt.Fprintf(&s, "%v", v)
addc = true
}
return s.String()
}

// GetRecorderFromContext will return the recoder that is
// in the context. If there isn't a recorder, then an invalid
// recorder will be returned. This can be checked with the
// IsValid() function on the recorder.
func GetRecorderFromContext(ctx context.Context) Recorder {

r, _ := ctx.Value(ContextRecorderKey).(Recorder)

if name, ok := ctx.Value(ContextRecorderTestnameKey).(string); ok {
r.Desc.Name = name
}

return r
}

var lck sync.Mutex
var recrds = make(map[string]struct {
fn string
rcrd *recorder
})

func cleanupFilename(fn string) string {
const replaceValues = ` []{}"'^%*&\,;?!()`
var nfn strings.Builder
for _, r := range strings.TrimSpace(strings.ToLower(fn)) {
switch {
case !unicode.IsPrint(r) || unicode.IsSpace(r):
// no opt
case strings.ContainsRune(replaceValues, r):
nfn.WriteRune('_')
default:
nfn.WriteRune(r)
}
}
return nfn.String()
}
func getFilenameDir(initialFilename string) (dir, filename string) {

initialFilename = cleanupFilename(initialFilename)
fullFilename := filepath.Clean(filepath.Join(DefaultOutputDir, initialFilename))
dir = filepath.Dir(fullFilename)
filename = filepath.Base(fullFilename)
return dir, filename
}

// AugmentRecorder is will create and configure a new recorder (if needed) to
// be used to track the debugging entries
// A Close call on the recoder should be supplied along with the
// AugmentRecorder call, this is usually done using a defer
// If the testFilename is "", then the function name of the calling function
// will be used as the filename for the database file.
func AugmentRecorder(rec Recorder, testFilename string) (Recorder, bool) {

if rec.IsValid() {
rec.IncrementCount()
return rec, false
}

if testFilename == "" {
testFilename = funcFileLine().Func
}
dir, filename := getFilenameDir(testFilename)

err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
panic(fmt.Sprintf("Failed to created dir %v:%v", dir, err))
}

lck.Lock()
defer lck.Unlock()
rcrd, ok := recrds[testFilename]
if !ok {
rcd, filename, err := NewRecorder(dir, filename)
if err != nil {
panic(err)
}
rcrd = struct {
fn string
rcrd *recorder
}{
fn: filename,
rcrd: &recorder{Interface: rcd},
}
recrds[testFilename] = rcrd
}
rcrd.rcrd.IncrementCount()
log.Println("Writing debugger output to", rcrd.fn)

return Recorder{
recorder: rcrd.rcrd,
Desc: TestDescription{
Name: uuid.NewRandom().String(),
},
}, true

}

// AugmentContext is will add and configure the recorder used to track the
// debugging entries into the context.
// A Close call should be supplied along with the AugmentContext call, this
// is usually done using a defer
// If the testFilename is "", then the function name of the calling function
// will be used as the filename for the database file.
func AugmentContext(ctx context.Context, testFilename string) context.Context {
if testFilename == "" {
testFilename = funcFileLine().Func
}

if rec, newRec := AugmentRecorder(GetRecorderFromContext(ctx), testFilename); newRec {
return context.WithValue(ctx, ContextRecorderKey, rec)
}
return ctx
}

// Close allows the recorder to release any resources it as, each
// AugmentContext call should have a mirroring Close call that is
// called at the end of the function.
func Close(ctx context.Context) {
GetRecorderFromContext(ctx).Close()
}

func CloseWait(ctx context.Context) {
GetRecorderFromContext(ctx).CloseWait()
}
func SetTestName(ctx context.Context, name string) context.Context {
return context.WithValue(
ctx,
ContextRecorderTestnameKey,
name,
)
}

// Record records the geom and descriptive attributes into the debugging system
func Record(ctx context.Context, geom interface{}, category string, descriptionFormat string, data ...interface{}) {
RecordFFLOn(GetRecorderFromContext(ctx), FFL(1), geom, category, descriptionFormat, data...)
}

// RecordOn records the geom and descriptive attributes into the debugging system
func RecordOn(rec Recorder, geom interface{}, category string, descriptionFormat string, data ...interface{}) {
RecordFFLOn(rec, FFL(1), geom, category, descriptionFormat, data...)
}

// RecordFFLOn records the geom and descriptive attributes into the debugging system with the give Func File Line values
func RecordFFLOn(rec Recorder, ffl FuncFileLineType, geom interface{}, category string, descriptionFormat string, data ...interface{}) {
if !rec.IsValid() {
return
}
description := fmt.Sprintf(descriptionFormat, data...)

rec.Record(
geom,
ffl,
TestDescription{
Category: category,
Description: description,
},
)
}
Loading