├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── atomctr └── atomctr.go ├── bitflag ├── README.md └── bitflag.go ├── bools ├── README.md └── bools.go ├── dirs ├── dirs.go └── fs.go ├── doc.go ├── fatomic └── fatomic.go ├── floats └── floats.go ├── go.mod ├── go.sum ├── indent └── indent.go ├── ints └── ints.go ├── ki ├── Makefile ├── admin.go ├── doc.go ├── flags.go ├── flags_string.go ├── io.go ├── ki.go ├── names.go ├── node.go ├── node_test.go ├── nodesignals_string.go ├── props.go ├── props_test.go ├── signal.go ├── signal_test.go ├── slice.go └── version.go ├── kit ├── README.md ├── convert.go ├── convert_test.go ├── embeds.go ├── embeds_test.go ├── enums.go ├── enums_test.go ├── errs.go ├── maps.go ├── maps_test.go ├── ptrs.go ├── ptrs_test.go ├── slices.go ├── slices_test.go ├── structs.go ├── testflags_string.go ├── type.go ├── typeandname.go └── types.go ├── logo ├── goki_logo.png └── goki_logo.svg ├── nptime ├── nptime.go └── nptime_test.go ├── runes └── runes.go ├── sliceclone └── sliceclone.go ├── toml ├── README.md └── toml.go └── walki ├── walki.go └── walki_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | strategy: 14 | matrix: 15 | go-version: [1.18.x] 16 | platform: [ubuntu-latest] # not macos-latest 17 | 18 | runs-on: ${{ matrix.platform }} 19 | steps: 20 | - name: Install Go 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | 25 | - name: Cache-Go 26 | uses: actions/cache@v1 27 | with: 28 | path: | 29 | ~/go/pkg/mod # Module download cache 30 | ~/.cache/go-build # Build cache (Linux) 31 | ~/Library/Caches/go-build # Build cache (Mac) 32 | '%LocalAppData%\go-build' # Build cache (Windows) 33 | 34 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 35 | restore-keys: | 36 | ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 37 | 38 | - name: Checkout code 39 | uses: actions/checkout@v2 40 | 41 | - name: Build 42 | run: | 43 | make build 44 | 45 | - name: Test 46 | run: | 47 | make test 48 | 49 | - name: Upload-Coverage 50 | if: matrix.platform == 'ubuntu-latest' 51 | run: bash <(curl -s https://codecov.io/bash) 52 | 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # mac thing 17 | .DS_Store 18 | 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.14.x 5 | - 1.15.x 6 | - master 7 | 8 | addons: 9 | apt_packages: 10 | - libgl1-mesa-dev 11 | - xorg-dev 12 | 13 | matrix: 14 | fast_finish: true 15 | allow_failures: 16 | - go: master 17 | 18 | env: 19 | - GO111MODULE=off 20 | - GO111MODULE=on 21 | 22 | script: 23 | - go get -d -t -v ./... 24 | - go build -v ./... 25 | - go test ./... 26 | 27 | notifications: 28 | email: 29 | recipients: 30 | - oreilly@ucdavis.edu 31 | on_success: change 32 | on_failure: always 33 | webhooks: 34 | urls: 35 | - https://ccnlab.zulipchat.com/api/v1/external/travis?api_key=7lfyOrci6YROdaxqqdDvQnNMWWQBtNuW&stream=emergent&topic=travis 36 | on_success: change 37 | on_failure: always 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, The GoKi Authors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Basic Go makefile 2 | 3 | GOCMD=go 4 | GOBUILD=$(GOCMD) build 5 | GOCLEAN=$(GOCMD) clean 6 | GOTEST=$(GOCMD) test -covermode=atomic -coverprofile=coverage.out 7 | GOGET=$(GOCMD) get 8 | 9 | # exclude python from std builds 10 | #DIRS=`go list ./... | grep -v python` 11 | DIRS=`go list ./...` 12 | 13 | all: build 14 | 15 | build: 16 | @echo "GO111MODULE = $(value GO111MODULE)" 17 | $(GOBUILD) -v $(DIRS) 18 | 19 | test: 20 | @echo "GO111MODULE = $(value GO111MODULE)" 21 | $(GOTEST) -v $(DIRS) 22 | 23 | clean: 24 | @echo "GO111MODULE = $(value GO111MODULE)" 25 | $(GOCLEAN) ./... 26 | 27 | fmts: 28 | gofmt -s -w . 29 | 30 | vet: 31 | @echo "GO111MODULE = $(value GO111MODULE)" 32 | $(GOCMD) vet $(DIRS) 33 | 34 | tidy: export GO111MODULE = on 35 | tidy: 36 | @echo "GO111MODULE = $(value GO111MODULE)" 37 | go mod tidy 38 | 39 | old: 40 | @echo "GO111MODULE = $(value GO111MODULE)" 41 | go list -u -m all 42 | 43 | mod-update: export GO111MODULE = on 44 | mod-update: 45 | @echo "GO111MODULE = $(value GO111MODULE)" 46 | go get -u ./... 47 | go mod tidy 48 | 49 | # gopath-update is for GOPATH to get most things updated. 50 | # need to call it in a target executable directory 51 | gopath-update: export GO111MODULE = off 52 | gopath-update: 53 | @echo "GO111MODULE = $(value GO111MODULE)" 54 | go get -u ./... 55 | 56 | mod-update: export GO111MODULE = on 57 | release: 58 | @echo "GO111MODULE = $(value GO111MODULE)" 59 | $(MAKE) -C ki release 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt tag](logo/goki_logo.png) 2 | 3 | Go language (golang) tree structure (ki = 木 = tree in Japanese) 4 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/goki/ki)](https://goreportcard.com/report/github.com/goki/ki) 6 | [![Go Reference](https://pkg.go.dev/badge/github.com/goki/ki.svg)](https://pkg.go.dev/github.com/goki/ki) 7 | [![CI](https://github.com/goki/ki/actions/workflows/ci.yml/badge.svg)](https://github.com/goki/ki/actions/workflows/ci.yml) 8 | [![Codecov](https://codecov.io/gh/goki/ki/branch/master/graph/badge.svg?token=Hw5cInAxY3)](https://codecov.io/gh/goki/ki) 9 | 10 | ## **IMPORTANT:** This is v1 of the GoKi system, which is maintained here for existing dependencies, but new development is taking place at https://github.com/cogentcore 11 | 12 | # Overview 13 | 14 | See the [Wiki](https://github.com/goki/ki/wiki) for more docs, and [Google Groups goki-gi](https://groups.google.com/forum/#!forum/goki-gi) emailing list. 15 | 16 | The **Tree** is one of the most flexible, widely-used data structures in programming, including the DOM structure at the core of a web browser, scene graphs for 3D and 2D graphics systems, JSON, XML, SVG, filesystems, programs themselves, etc. This is because trees can capture most relevant forms of *structure* (hierarchical groupings, categories, relationships, etc) and are most powerful when they are fully *generative* -- arbitrary new types can be inserted flexibly. 17 | 18 | GoKi provides a general-purpose tree container type, that can support all of these applications, by embedding and extending the `Node` struct type that implements the `Ki` (Ki = Tree in Japanese) `interface`. Unlike many cases in Go, the need to be able to arbitrarily extend the type space of nodes in the tree within a consistent API, means that the more traditional object-oriented model works best here, with a single common base type, and derived types that handle diverse cases (e.g., different types of widgets in a GUI). GoKi stores a Ki interface of each node, enabling correct virtual function calling on these derived types. 19 | 20 | A virtue of using an appropriate data representation is that some important operations can be performed particularly concisely and efficiently when they are naturally supported by the data structure. For example, matrices and vectors as supported by numpy or MATLAB provide a concise high-level language for expressing many algorithms. 21 | 22 | For trees, GoKi leverages the tree structure for automatically computing the appropriate extent of a scenegraph that needs to be updated, with an arbitrary sequence of individual operations, by propagating updating flags through the tree, and tracking the "high water mark" (see UpdateStart / End). This makes the GoGi GUI efficient in terms of what needs to be redrawn, while keeping the code local and simple. 23 | 24 | In addition, GoKi provides functions that traverse the tree in the usual relevant ways ("natural" me-first depth-first, me-last depth-first, and breadth-first) and take a `func` function argument, so you can easily apply a common operation across the whole tree in a transparent and self-contained manner, like this: 25 | 26 | ```go 27 | func (n *MyNode) DoSomethingOnMyTree() { 28 | n.FuncDownMeFirst(0, nil, func(k Ki, level int, d interface{}) bool { 29 | mn := KiToMyNode(k) // function converts a Ki to relevant node type -- you must write 30 | mn.DoSomething() 31 | ... 32 | return ki.Continue // return value determines whether tree traversal continues or not 33 | }) 34 | } 35 | ``` 36 | 37 | Many operations are naturally expressed in terms of these traversal algorithms. 38 | 39 | Three core GoKi features include: 40 | 41 | * A `Signal` mechanism that allows nodes to communicate changes and other events to arbitrary lists of other nodes (similar to the signals and slots from Qt). 42 | 43 | * `UpdateStart()` and `UpdateEnd()` functions that wrap around code that changes the tree structure or contents -- these automatically and efficiently determine the highest level node that was affected by changes, and only that highest node sends an `Updated` signal. This allows arbitrarily nested modifications to proceed independently, each wrapped in their own Start / End blocks, with the optimal minimal update signaling automatically computed. 44 | 45 | * `ConfigChildren` uses a list of types and names and performs a minimal, efficient update of the children of a node to configure them to match (including no changes if already configured accordingly). This is used during loading from JSON, and extensively in the `GoGi` GUI system to efficiently re-use existing tree elements. There is often complex logic to determine what elements need to be present in a Widget, so separating that out from then configuring the elements that actually are present is efficient and simplifies the code. 46 | 47 | In addition, Ki nodes support a general-purpose `Props` property `map`, and the `kit` (Ki Types) package provides a `TypeRegistry` and an `EnumRegistry`, along with various `reflect` utilities, to enable fully-automatic saving / loading of Ki trees from JSON or XML, including converting const int (enum) values to / from strings so those numeric values can change in the code without invalidating existing files. 48 | 49 | Ki Nodes can be used as fields in a struct -- they function much like pre-defined Children elements, and all the standard FuncDown* iterators traverse the fields automatically. The Ki Init function automatically names these structs with their field names, and sets the parent to the parent struct. This was essential in the GoKi framework to support separate Widget Parts independent of the larger scenegraph. 50 | 51 | ## GoGi Graphical Interface and Gide IDE App 52 | 53 | The first and most important application of GoKi is the [GoGi](https://github.com/goki/gi) graphical interface system, in the `gi` package, and the [Gide](https://github.com/goki/gide) IDE built on top of GoGi. The scene graph of Ki elements automatically drives minimal refresh updates, and the signaling framework supports gui event delivery and e.g., the "onclick" event signaling from the `Button` widget, etc. In short, GoGi provides a complete interactive 2D and 3D GUI environment in native Go, in a compact codebase. Part of this is the natural elegance of Go, but GoKi enhances that by providing the robust natural primitives needed to express all the GUI functionality. Because GoGi is based around standard CSS styles, SVG rendering, and supports all the major HTML elements, it could even provide a lightweight web browser: [Glide](https://github.com/goki/glide). 54 | 55 | The [GoPi](https://github.com/goki/pi) interactive parsing framework also leverages GoKi trees to represent the AST (abstract syntax tree) of programs in different langauges. Further, the parser grammar itself is written (in a GUI interactive way) as a tree of parsing elements using Ki nodes. 56 | 57 | # Code Map 58 | 59 | * `kit` package: `kit.Types` `TypeRegistry` provides name-to-type map for looking up types by name, and types can have default properties. `kit.Enums` `EnumRegistry` provides enum (const int) <-> string conversion, including `bitflag` enums. Also has robust generic `kit.ToInt` `kit.ToFloat` etc converters from `interface{}` to specific type, for processing properties, and several utilities in `embeds.go` for managing embedded structure types (e.g., `TypeEmbeds` checks if one type embeds another, and `EmbeddedStruct` returns the embedded struct from a given struct, providing flexible access to elements of an embedded type hierarchy -- there are also methods for navigating the flattened list of all embedded fields within a struct). Also has a `kit.Type` struct that supports saving / loading of type information using type names. 60 | 61 | * `walki` package provides tree-walking methods for more ad-hoc, special-case tree traversal, as compared to the standard Func* methods on Ki itself. 62 | 63 | * `bitflag` package: simple bit flag setting, checking, and clearing methods that take bit position args as ints (from const int eunum iota's) and do the bit shifting from there 64 | 65 | * `ki.go` = `Ki` interface for all major tree node functionality. 66 | 67 | * `slice.go` = `ki.Slice []Ki` supports saving / loading of Ki objects in a slice, by recording the size and types of elements in the slice -- requires `kit.Types` type registry to lookup types by name. 68 | 69 | * `props.go` = `ki.Props map[string]interface{}` supports saving / loading of property values using actual `struct` types and named const int enums, using the `kit` type registries. Used for CSS styling in `GoGi`. 70 | 71 | * `signal.go` = `Signal` that calls function on a receiver Ki objects that have been previously `Connect`ed to the signal -- also supports signal type so the same signal sender can send different types of signals over the same connection -- used for signaling changes in tree structure, and more general tree updating signals. 72 | 73 | # Status 74 | 75 | * Feb, 2021: version 1.1.0 reflects major simplification pass to reduce API footprint and remove separate Unique names (names should in general be unique -- add a separate non-unique name where needed). Now that GoGi etc is complete, could get rid if quite a few things. 76 | 77 | * April, 2020: version 1.0.0 release -- all stable and well tested. 78 | 79 | # Simple Example 80 | 81 | See `ki/node_test.go` for lots of simple usage examples. Here's some code from there that makes a tree with a parent and 2 children. 82 | 83 | ```go 84 | parent := NodeEmbed{} 85 | parent.InitName(&parent, "par1") // root must be initialized -- this also names it. 86 | typ := reflect.TypeOf(parent) 87 | parent.AddNewChild(typ, "child1") // Add etc methods auto-initialize children 88 | parent.AddNewChild(typ, "child2") 89 | 90 | // traverse the tree calling the parent node first and then its children, recursively 91 | // "natural" order 92 | parent.FuncDownMeFirst(0, nil, func(k Ki, level int, d interface{}) bool { 93 | fmt.Printf("level: %d node: %s\n", level, k.Path()) 94 | return ki.Continue 95 | }) 96 | ``` 97 | 98 | # Trick for fast finding in a slice 99 | 100 | GoKi takes an extra starting index arg for all methods that lookup a value in a slice, such as ChildByName. The search for the item starts at that index, and goes up and down from there. Thus, if you have any idea where the item might be in the list, it can save (considerable for large lists) time finding it. 101 | 102 | Furthermore, it enables a robust optimized lookup map that remembers these indexes for each item, but then always searches from the index, so it is always correct under list modifications, but if the list is unchanged, then it is very efficient, and does not require saving pointers, which minimizes any impact on the GC, prevents stale pointers, etc. 103 | 104 | The `IndexInParent()` method uses this trick, using the cached `Node.index` value. 105 | 106 | Here's example code for a separate Find method where the indexes are stored in a map: 107 | 108 | ```Go 109 | // FindByName finds item by name, using cached indexes for speed 110 | func (ob *Obj) FindByName(nm string) *Obj { 111 | if sv.FindIdxs == nil { 112 | ob.FindIdxs = make(map[string]int) // field on object 113 | } 114 | idx, has := ob.FindIdxs[nm] 115 | if !has { 116 | idx = len(ob.Kids) / 2 // start in middle first time 117 | } 118 | idx, has = ob.Kids.IndexByName(nm, idx) 119 | if has { 120 | ob.FindIdxs[nm] = idx 121 | return ob.Kids[idx].(*Obj) 122 | } 123 | delete(ob.FindIdxs, nm) // must have been deleted 124 | return nil 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /atomctr/atomctr.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package atomctr implements basic atomic int64 counter, used e.g., for 6 | // update counter on Ki Node 7 | package atomctr 8 | 9 | import ( 10 | "sync/atomic" 11 | ) 12 | 13 | // Ctr implements basic atomic int64 counter, used e.g., for update counter on Ki Node 14 | type Ctr int64 15 | 16 | // increment counter 17 | func (a *Ctr) Add(inc int64) int64 { 18 | return atomic.AddInt64((*int64)(a), inc) 19 | } 20 | 21 | // decrement counter 22 | func (a *Ctr) Sub(dec int64) int64 { 23 | return atomic.AddInt64((*int64)(a), -dec) 24 | } 25 | 26 | // inc = ++ 27 | func (a *Ctr) Inc() int64 { 28 | return atomic.AddInt64((*int64)(a), 1) 29 | } 30 | 31 | // dec = -- 32 | func (a *Ctr) Dec() int64 { 33 | return atomic.AddInt64((*int64)(a), -1) 34 | } 35 | 36 | // current value 37 | func (a *Ctr) Value() int64 { 38 | return atomic.LoadInt64((*int64)(a)) 39 | } 40 | 41 | // set current value 42 | func (a *Ctr) Set(val int64) { 43 | atomic.StoreInt64((*int64)(a), val) 44 | } 45 | 46 | // swap new value in and return old value 47 | func (a *Ctr) Swap(val int64) int64 { 48 | return atomic.SwapInt64((*int64)(a), val) 49 | } 50 | -------------------------------------------------------------------------------- /bitflag/README.md: -------------------------------------------------------------------------------- 1 | # bitflag 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/goki/ki/bitflag)](https://goreportcard.com/report/github.com/goki/ki/bitflag) 4 | [![GoDoc](https://godoc.org/github.com/goki/ki/bitflag?status.svg)](http://godoc.org/github.com/goki/ki/bitflag) 5 | 6 | Package `bitflag` provides simple bit flag setting, checking, and clearing 7 | methods that take bit position args as ints (from const int eunum iota's) 8 | and do the bit shifting from there -- although a tiny bit slower, the 9 | convenience of maintaining ordinal lists of bit positions greatly outweighs 10 | that cost -- see kit type registry for further enum management functions 11 | -------------------------------------------------------------------------------- /bitflag/bitflag.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package bitflag provides simple bit flag setting, checking, and clearing 7 | methods that take bit position args as ints (from const int eunum iota's) 8 | and do the bit shifting from there -- although a tiny bit slower, the 9 | convenience of maintaining ordinal lists of bit positions greatly outweighs 10 | that cost -- see kit type registry for further enum management functions 11 | */ 12 | package bitflag 13 | 14 | import "sync/atomic" 15 | 16 | // we assume 64bit bitflags by default -- 32 bit methods specifically marked 17 | 18 | //////////////////////////////////////////////////////////////////////// 19 | // Core Mask Impl methods, take the full bitmask 20 | 21 | // SetMask sets bits in mask 22 | func SetMask(bits *int64, mask int64) { 23 | *bits |= mask 24 | } 25 | 26 | // SetMaskAtomic sets bits in mask 27 | // using atomic compare-and-swap loop, safe for concurrent access 28 | func SetMaskAtomic(bits *int64, mask int64) { 29 | for { 30 | cr := atomic.LoadInt64(bits) 31 | nw := cr | mask 32 | if atomic.CompareAndSwapInt64(bits, cr, nw) { 33 | break 34 | } 35 | } 36 | } 37 | 38 | // ClearMask clears all of the bits in the mask 39 | func ClearMask(bits *int64, mask int64) { 40 | *bits &^= mask 41 | } 42 | 43 | // ClearMaskAtomic clears all of the bits in the mask 44 | // using atomic compare-and-swap loop, safe for concurrent access 45 | func ClearMaskAtomic(bits *int64, mask int64) { 46 | for { 47 | cr := atomic.LoadInt64(bits) 48 | nw := cr &^ mask 49 | if atomic.CompareAndSwapInt64(bits, cr, nw) { 50 | break 51 | } 52 | } 53 | } 54 | 55 | // HasAnyMask checks if *any* of the bits in mask are set (logical OR) 56 | func HasAnyMask(bits, mask int64) bool { 57 | return bits&mask != 0 58 | } 59 | 60 | // HasAllMask checks if *all* of the bits in mask are set (logical AND) 61 | func HasAllMask(bits, mask int64) bool { 62 | return bits&mask == mask 63 | } 64 | 65 | // HasAnyMaskAtomic checks if *any* of the bits in mask are set (logical OR) 66 | // using atomic compare-and-swap loop, safe for concurrent access 67 | func HasAnyMaskAtomic(bits *int64, mask int64) bool { 68 | return (atomic.LoadInt64(bits) & mask) != 0 69 | } 70 | 71 | // HasAllMaskAtomic checks if *all* of the bits in mask are set (logical AND) 72 | // using atomic compare-and-swap loop, safe for concurrent access 73 | func HasAllMaskAtomic(bits *int64, mask int64) bool { 74 | return (atomic.LoadInt64(bits) & mask) == mask 75 | } 76 | 77 | //////////////////////////////////////////////////////////////////////// 78 | // Convenience methods for ordinal bitflags 79 | 80 | // Mask makes a mask for checking multiple different flags 81 | func Mask(flags ...int) int64 { 82 | var mask int64 83 | for _, f := range flags { 84 | mask |= 1 << uint32(f) 85 | } 86 | return mask 87 | } 88 | 89 | // Set sets bit value(s) for ordinal bit position flags 90 | func Set(bits *int64, flags ...int) { 91 | SetMask(bits, Mask(flags...)) 92 | } 93 | 94 | // SetAtomic sets bit value(s) for ordinal bit position flags 95 | // using atomic compare-and-swap loop, safe for concurrent access 96 | func SetAtomic(bits *int64, flags ...int) { 97 | SetMaskAtomic(bits, Mask(flags...)) 98 | } 99 | 100 | // SetState sets or clears bit value(s) depending on state (on / off) for 101 | // ordinal bit position flags 102 | func SetState(bits *int64, state bool, flags ...int) { 103 | if state { 104 | Set(bits, flags...) 105 | } else { 106 | Clear(bits, flags...) 107 | } 108 | } 109 | 110 | // SetStateAtomic sets or clears bit value(s) depending on state (on / off) 111 | // for ordinal bit position flags, protected by atomic -- safe for concurrent access 112 | func SetStateAtomic(bits *int64, state bool, flags ...int) { 113 | if state { 114 | SetAtomic(bits, flags...) 115 | } else { 116 | ClearAtomic(bits, flags...) 117 | } 118 | } 119 | 120 | // Clear clears bit value(s) for ordinal bit position flags 121 | func Clear(bits *int64, flags ...int) { 122 | ClearMask(bits, Mask(flags...)) 123 | } 124 | 125 | // ClearAtomic clears bit value(s) for ordinal bit position flags 126 | // using atomic compare-and-swap loop, safe for concurrent access 127 | func ClearAtomic(bits *int64, flags ...int) { 128 | ClearMaskAtomic(bits, Mask(flags...)) 129 | } 130 | 131 | // Has checks if given bit value is set for ordinal bit position flag 132 | func Has(bits int64, flag int) bool { 133 | return bits&(1<= 0; i-- { 63 | fn := files[i] 64 | ext := filepath.Ext(fn.Name()) 65 | keep := false 66 | for _, ex := range exts { 67 | if strings.EqualFold(ext, ex) { 68 | keep = true 69 | break 70 | } 71 | } 72 | if !keep { 73 | files = append(files[:i], files[i+1:]...) 74 | } 75 | } 76 | return files 77 | } 78 | 79 | // ExtFileNames returns all the file names with given extension(s) in directory 80 | // in sorted order (if exts is empty then all files are returned) 81 | func ExtFileNames(path string, exts []string) []string { 82 | f, err := os.Open(path) 83 | if err != nil { 84 | return nil 85 | } 86 | files, err := f.Readdirnames(-1) 87 | f.Close() 88 | if err != nil { 89 | return nil 90 | } 91 | if len(exts) == 0 { 92 | sort.StringSlice(files).Sort() 93 | return files 94 | } 95 | sz := len(files) 96 | if sz == 0 { 97 | return nil 98 | } 99 | for i := sz - 1; i >= 0; i-- { 100 | fn := files[i] 101 | ext := filepath.Ext(fn) 102 | keep := false 103 | for _, ex := range exts { 104 | if strings.EqualFold(ext, ex) { 105 | keep = true 106 | break 107 | } 108 | } 109 | if !keep { 110 | files = append(files[:i], files[i+1:]...) 111 | } 112 | } 113 | sort.StringSlice(files).Sort() 114 | return files 115 | } 116 | 117 | // Dirs returns a slice of all the directories within a given directory 118 | func Dirs(path string) []string { 119 | files, err := ioutil.ReadDir(path) 120 | if err != nil { 121 | return nil 122 | } 123 | 124 | var fnms []string 125 | for _, fi := range files { 126 | if fi.IsDir() { 127 | fnms = append(fnms, fi.Name()) 128 | } 129 | } 130 | return fnms 131 | } 132 | 133 | // LatestMod returns the latest (most recent) modification time for any of the 134 | // files in the directory (optionally filtered by extension(s) if exts != nil) 135 | // if no files or error, returns zero time value 136 | func LatestMod(path string, exts []string) time.Time { 137 | tm := time.Time{} 138 | files := ExtFiles(path, exts) 139 | if len(files) == 0 { 140 | return tm 141 | } 142 | for _, fi := range files { 143 | if fi.ModTime().After(tm) { 144 | tm = fi.ModTime() 145 | } 146 | } 147 | return tm 148 | } 149 | 150 | // AllFiles returns a slice of all the files, recursively, within a given directory 151 | // Due to the nature of the filepath.Walk function, the first entry will be the 152 | // directory itself, for reference -- just skip past that if you don't need it. 153 | func AllFiles(path string) ([]string, error) { 154 | var fnms []string 155 | er := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 156 | if err != nil { 157 | return err 158 | } 159 | fnms = append(fnms, path) 160 | return nil 161 | }) 162 | return fnms, er 163 | } 164 | 165 | // HasFile returns true if given directory has given file (exact match) 166 | func HasFile(path, file string) bool { 167 | files, err := ioutil.ReadDir(path) 168 | if err != nil { 169 | return false 170 | } 171 | for _, fn := range files { 172 | if fn.Name() == file { 173 | return true 174 | } 175 | } 176 | return false 177 | } 178 | 179 | // note: rejected from std lib, but often need: https://github.com/golang/go/issues/25012 180 | // https://github.com/golang/go/issues/5366 181 | 182 | // SplitExt returns the base of the file name without extension, and the extension 183 | func SplitExt(fname string) (fbase, ext string) { 184 | ext = filepath.Ext(fname) 185 | fbase = strings.TrimSuffix(fname, ext) 186 | return 187 | } 188 | 189 | // FindFileOnPaths attempts to locate given file on given list of paths, 190 | // returning the full Abs path to file if found, else error 191 | func FindFileOnPaths(paths []string, file string) (string, error) { 192 | for _, path := range paths { 193 | filePath := filepath.Join(path, file) 194 | ok, _ := FileExists(filePath) 195 | if ok { 196 | return filePath, nil 197 | } 198 | } 199 | return "", fmt.Errorf("FindFileOnPaths: unable to find file: %s on paths: %v\n", file, paths) 200 | } 201 | 202 | // FileExists checks whether given file exists, returning true if so, 203 | // false if not, and error if there is an error in accessing the file. 204 | func FileExists(filePath string) (bool, error) { 205 | fileInfo, err := os.Stat(filePath) 206 | if err == nil { 207 | return !fileInfo.IsDir(), nil 208 | } 209 | if errors.Is(err, os.ErrNotExist) { 210 | return false, nil 211 | } 212 | return false, err 213 | } 214 | 215 | // FileExistsFS checks whether given file exists, returning true if so, 216 | // false if not, and error if there is an error in accessing the file. 217 | func FileExistsFS(fsys fs.FS, filePath string) (bool, error) { 218 | if fsys, ok := fsys.(fs.StatFS); ok { 219 | fileInfo, err := fsys.Stat(filePath) 220 | if err == nil { 221 | return !fileInfo.IsDir(), nil 222 | } 223 | if errors.Is(err, fs.ErrNotExist) { 224 | return false, nil 225 | } 226 | return false, err 227 | } 228 | fp, err := fsys.Open(filePath) 229 | if err == nil { 230 | fp.Close() 231 | return true, nil 232 | } 233 | if errors.Is(err, fs.ErrNotExist) { 234 | return false, nil 235 | } 236 | return false, err 237 | } 238 | -------------------------------------------------------------------------------- /dirs/fs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dirs 6 | 7 | import ( 8 | "io/fs" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | // DirFS returns the directory part of given file path as an os.DirFS 14 | // and the filename as a string. These can then be used to access the file 15 | // using the FS-based interface, consistent with embed and other use-cases. 16 | func DirFS(fpath string) (fs.FS, string, error) { 17 | fabs, err := filepath.Abs(fpath) 18 | if err != nil { 19 | return nil, "", err 20 | } 21 | dir, fname := filepath.Split(fabs) 22 | dfs := os.DirFS(dir) 23 | return dfs, fname, nil 24 | } 25 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package ki provides the top-level repository for GoKi Trees: Ki = Tree in Japanese, and 7 | "Key" in English -- powerful tree structures supporting scenegraphs, programs, 8 | parsing, etc. 9 | 10 | The sub-packages contain all the relevant code: 11 | 12 | * ki: is the main Ki interface and Node implementation thereof. 13 | 14 | * kit: is a type registry that ki uses in various ways and provides 15 | useful type-level properties that are used in the GoGi GUI. It also 16 | is a powerful 'kit for dealing with Go's reflect system. 17 | 18 | * ints, floats, dirs, bitflag, atomctr, indent all provide basic 19 | Go infrastructure that one could argue should have been in the 20 | standard library, but isn't.. 21 | */ 22 | package ki 23 | -------------------------------------------------------------------------------- /fatomic/fatomic.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, The Emergent Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package fatomic provides floating-point atomic operations 6 | package fatomic 7 | 8 | import ( 9 | "math" 10 | "sync/atomic" 11 | "unsafe" 12 | ) 13 | 14 | // from: 15 | // https://stackoverflow.com/questions/27492349/go-atomic-addfloat32 16 | 17 | // AddFloat32 adds given increment to a float32 value atomically 18 | func AddFloat32(val *float32, inc float32) (nval float32) { 19 | for { 20 | old := *val 21 | nval = old + inc 22 | if atomic.CompareAndSwapUint32( 23 | (*uint32)(unsafe.Pointer(val)), 24 | math.Float32bits(old), 25 | math.Float32bits(nval), 26 | ) { 27 | break 28 | } 29 | } 30 | return 31 | } 32 | 33 | // AddFloat64 adds given increment to a float64 value atomically 34 | func AddFloat64(val *float64, inc float64) (nval float64) { 35 | for { 36 | old := *val 37 | nval = old + inc 38 | if atomic.CompareAndSwapUint64( 39 | (*uint64)(unsafe.Pointer(val)), 40 | math.Float64bits(old), 41 | math.Float64bits(nval), 42 | ) { 43 | break 44 | } 45 | } 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /floats/floats.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | package floats provides a standard Floater interface and all the std math functions 7 | 8 | defined on Floater types. Furthermore, fully generic slice sort and 9 | conversion methods in the kit type kit package attempt to use this interface, 10 | before falling back on reflection. If you have a struct that can be converted 11 | into an float64, then this is the only way to allow it to be sorted using those 12 | generic functions, as the reflect.Kind fallback will fail. 13 | */ 14 | package floats 15 | 16 | import "math" 17 | 18 | // Floater converts a type from a float64, used in kit.ToFloat function and in 19 | // sorting comparisons -- tried first in sorting 20 | type Floater interface { 21 | Float() float64 22 | } 23 | 24 | // FloatSetter is Floater that also supports setting the value from a float64. 25 | // Satisfying this interface requires a pointer to the underlying type. 26 | type FloatSetter interface { 27 | Floater 28 | FromFloat(val float64) 29 | } 30 | 31 | /////////////////////////////////////////////////////// 32 | // math wrappers 33 | 34 | func Abs(x Floater) float64 { 35 | return math.Abs(x.Float()) 36 | } 37 | func Acos(x Floater) float64 { 38 | return math.Acos(x.Float()) 39 | } 40 | func Acosh(x Floater) float64 { 41 | return math.Acosh(x.Float()) 42 | } 43 | func Asin(x Floater) float64 { 44 | return math.Asin(x.Float()) 45 | } 46 | func Asinh(x Floater) float64 { 47 | return math.Asinh(x.Float()) 48 | } 49 | func Atan(x Floater) float64 { 50 | return math.Atan(x.Float()) 51 | } 52 | func Atan2(y, x Floater) float64 { 53 | return math.Atan2(x.Float(), y.Float()) 54 | } 55 | func Atanh(x Floater) float64 { 56 | return math.Atanh(x.Float()) 57 | } 58 | func Cbrt(x Floater) float64 { 59 | return math.Cbrt(x.Float()) 60 | } 61 | func Ceil(x Floater) float64 { 62 | return math.Ceil(x.Float()) 63 | } 64 | func Copysign(x, y Floater) float64 { 65 | return math.Copysign(x.Float(), y.Float()) 66 | } 67 | func Cos(x Floater) float64 { 68 | return math.Cos(x.Float()) 69 | } 70 | func Cosh(x Floater) float64 { 71 | return math.Cosh(x.Float()) 72 | } 73 | func Dim(x, y Floater) float64 { 74 | return math.Dim(x.Float(), y.Float()) 75 | } 76 | func Erf(x Floater) float64 { 77 | return math.Erf(x.Float()) 78 | } 79 | func Erfc(x Floater) float64 { 80 | return math.Erfc(x.Float()) 81 | } 82 | func Erfcinv(x Floater) float64 { 83 | return math.Erfcinv(x.Float()) 84 | } 85 | func Erfinv(x Floater) float64 { 86 | return math.Erfinv(x.Float()) 87 | } 88 | func Exp(x Floater) float64 { 89 | return math.Exp(x.Float()) 90 | } 91 | func Exp2(x Floater) float64 { 92 | return math.Exp2(x.Float()) 93 | } 94 | func Expm1(x Floater) float64 { 95 | return math.Expm1(x.Float()) 96 | } 97 | func Floor(x Floater) float64 { 98 | return math.Floor(x.Float()) 99 | } 100 | func Frexp(f Floater) (frac float64, exp int) { 101 | return math.Frexp(f.Float()) 102 | } 103 | func Gamma(x Floater) float64 { 104 | return math.Gamma(x.Float()) 105 | } 106 | func Hypot(p, q Floater) float64 { 107 | return math.Hypot(p.Float(), q.Float()) 108 | } 109 | func Ilogb(x Floater) int { 110 | return math.Ilogb(x.Float()) 111 | } 112 | func IsInf(f Floater, sign int) bool { 113 | return math.IsInf(f.Float(), sign) 114 | } 115 | func IsNaN(f Floater) (is bool) { 116 | return math.IsNaN(f.Float()) 117 | } 118 | func J0(x Floater) float64 { 119 | return math.J0(x.Float()) 120 | } 121 | func J1(x Floater) float64 { 122 | return math.J1(x.Float()) 123 | } 124 | func Jn(n int, x Floater) float64 { 125 | return math.Jn(n, x.Float()) 126 | } 127 | func Ldexp(frac Floater, exp int) float64 { 128 | return math.Ldexp(frac.Float(), exp) 129 | } 130 | func Lgamma(x Floater) (lgamma float64, sign int) { 131 | return math.Lgamma(x.Float()) 132 | } 133 | func Log(x Floater) float64 { 134 | return math.Log(x.Float()) 135 | } 136 | func Log10(x Floater) float64 { 137 | return math.Log10(x.Float()) 138 | } 139 | func Log1p(x Floater) float64 { 140 | return math.Log1p(x.Float()) 141 | } 142 | func Log2(x Floater) float64 { 143 | return math.Log2(x.Float()) 144 | } 145 | func Logb(x Floater) float64 { 146 | return math.Logb(x.Float()) 147 | } 148 | func Max(x, y Floater) float64 { 149 | return math.Max(x.Float(), y.Float()) 150 | } 151 | func Min(x, y Floater) float64 { 152 | return math.Min(x.Float(), y.Float()) 153 | } 154 | func Mod(x, y Floater) float64 { 155 | return math.Mod(x.Float(), y.Float()) 156 | } 157 | func Modf(f Floater) (int float64, frac float64) { 158 | return math.Modf(f.Float()) 159 | } 160 | func Nextafter(x, y Floater) (r float64) { 161 | return math.Nextafter(x.Float(), y.Float()) 162 | } 163 | func Pow(x, y Floater) float64 { 164 | return math.Pow(x.Float(), y.Float()) 165 | } 166 | func Remainder(x, y Floater) float64 { 167 | return math.Remainder(x.Float(), y.Float()) 168 | } 169 | func Round(x Floater) float64 { 170 | return math.Round(x.Float()) 171 | } 172 | func RoundToEven(x Floater) float64 { 173 | return math.RoundToEven(x.Float()) 174 | } 175 | func Signbit(x Floater) bool { 176 | return math.Signbit(x.Float()) 177 | } 178 | func Sin(x Floater) float64 { 179 | return math.Sin(x.Float()) 180 | } 181 | func Sincos(x Floater) (sin, cos float64) { 182 | return math.Sincos(x.Float()) 183 | } 184 | func Sinh(x Floater) float64 { 185 | return math.Sinh(x.Float()) 186 | } 187 | func Sqrt(x Floater) float64 { 188 | return math.Sqrt(x.Float()) 189 | } 190 | func Tan(x Floater) float64 { 191 | return math.Tan(x.Float()) 192 | } 193 | func Tanh(x Floater) float64 { 194 | return math.Tanh(x.Float()) 195 | } 196 | func Trunc(x Floater) float64 { 197 | return math.Trunc(x.Float()) 198 | } 199 | func Y0(x Floater) float64 { 200 | return math.Y0(x.Float()) 201 | } 202 | func Y1(x Floater) float64 { 203 | return math.Y1(x.Float()) 204 | } 205 | func Yn(n int, x Floater) float64 { 206 | return math.Yn(n, x.Float()) 207 | } 208 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goki/ki 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.3.2 7 | github.com/jinzhu/copier v0.3.2 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= 2 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/jinzhu/copier v0.3.2 h1:QdBOCbaouLDYaIPFfi1bKv5F5tPpeTwXe4sD0jqtz5w= 4 | github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= 5 | -------------------------------------------------------------------------------- /indent/indent.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package indent provides trivial indentation generation methods: Tabs, 7 | Spaces, and Indent with a selector. Also bytes versions. 8 | Just for clarity, simplicity. 9 | */ 10 | package indent 11 | 12 | import ( 13 | "bytes" 14 | "strings" 15 | ) 16 | 17 | // Char is the type of indentation character to use 18 | type Char int 19 | 20 | const ( 21 | // Tab uses tab for indentation 22 | Tab Char = iota 23 | 24 | // Space uses spaces for indentation 25 | Space 26 | ) 27 | 28 | // Tabs returns a string of n tabs 29 | func Tabs(n int) string { 30 | return strings.Repeat("\t", n) 31 | } 32 | 33 | // TabBytes returns []byte of n tabs 34 | func TabBytes(n int) []byte { 35 | return bytes.Repeat([]byte("\t"), n) 36 | } 37 | 38 | // Spaces returns a string of n*width spaces 39 | func Spaces(n, width int) string { 40 | return strings.Repeat(" ", n*width) 41 | } 42 | 43 | // SpaceBytes returns a []byte of n*width spaces 44 | func SpaceBytes(n, width int) []byte { 45 | return bytes.Repeat([]byte(" "), n*width) 46 | } 47 | 48 | // String returns a string of n tabs or n*width spaces depending on the indent char 49 | func String(ich Char, n, width int) string { 50 | if ich == Tab { 51 | return Tabs(n) 52 | } 53 | return Spaces(n, width) 54 | } 55 | 56 | // Bytes returns []byte of n tabs or n*width spaces depending on the indent char 57 | func Bytes(ich Char, n, width int) []byte { 58 | if ich == Tab { 59 | return TabBytes(n) 60 | } 61 | return SpaceBytes(n, width) 62 | } 63 | 64 | // Len returns the length of the indent string given indent char and indent level 65 | func Len(ich Char, n, width int) int { 66 | if ich == Tab { 67 | return n 68 | } 69 | return n * width 70 | } 71 | -------------------------------------------------------------------------------- /ints/ints.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package ints provides a standard Inter interface and basic functions 7 | defined on Inter types that support core things like Max, Min, Abs. 8 | Furthermore, fully generic slice sort and conversion methods in the kit type 9 | kit package attempt to use this interface, before falling back on reflection. 10 | If you have a struct that can be converted into an int64, then this is the 11 | only way to allow it to be sorted using those generic functions, as the 12 | reflect.Kind fallback will fail. 13 | 14 | It also includes Max, Min, Abs for builtin int64, int32 types. 15 | */ 16 | package ints 17 | 18 | import "math" 19 | 20 | // Inter converts a type from an int64, used in kit.ToInt and in sorting 21 | // comparisons. See also Floater in floats package. 22 | type Inter interface { 23 | Int() int64 24 | } 25 | 26 | // IntSetter is an Inter that can also be set from an int. Satisfying this 27 | // interface requires a pointer to the underlying type. 28 | type IntSetter interface { 29 | Inter 30 | FromInt(val int64) 31 | } 32 | 33 | //////////////////////////////////// 34 | // Inter 35 | 36 | // Max computes the maximum of the two Inter args 37 | func Max(a, b Inter) Inter { 38 | if a.Int() > b.Int() { 39 | return a 40 | } 41 | return b 42 | } 43 | 44 | // Min computes the minimum of the two Inter args 45 | func Min(a, b Inter) Inter { 46 | if a.Int() < b.Int() { 47 | return a 48 | } 49 | return b 50 | } 51 | 52 | // Abs computes the absolute value of the given value 53 | func Abs(a Inter) int64 { 54 | if a.Int() < 0 { 55 | return -a.Int() 56 | } 57 | return a.Int() 58 | } 59 | 60 | //////////////////////////////////// 61 | // int 62 | 63 | // MaxInt computes the maximum of the two int args 64 | func MaxInt(a, b int) int { 65 | if a > b { 66 | return a 67 | } 68 | return b 69 | } 70 | 71 | // MinInt computes the minimum of the two int args 72 | func MinInt(a, b int) int { 73 | if a < b { 74 | return a 75 | } 76 | return b 77 | } 78 | 79 | // AbsInt computes the absolute value of the given value 80 | func AbsInt(a int) int { 81 | if a < 0 { 82 | return -a 83 | } 84 | return a 85 | } 86 | 87 | // ClipInt clips int within min, max range (max exclusive, min inclusive) 88 | func ClipInt(a, min, max int) int { 89 | if a >= max { 90 | return max - 1 91 | } 92 | if a < min { 93 | return min 94 | } 95 | return a 96 | } 97 | 98 | //////////////////////////////////// 99 | // int64 100 | 101 | // Max64 computes the maximum of the two int64 args 102 | func Max64(a, b int64) int64 { 103 | if a > b { 104 | return a 105 | } 106 | return b 107 | } 108 | 109 | // Min64 computes the minimum of the two int64 args 110 | func Min64(a, b int64) int64 { 111 | if a < b { 112 | return a 113 | } 114 | return b 115 | } 116 | 117 | // Abs64 computes the absolute value of the given value 118 | func Abs64(a int64) int64 { 119 | if a < 0 { 120 | return -a 121 | } 122 | return a 123 | } 124 | 125 | //////////////////////////////////// 126 | // int32 127 | 128 | // Max32 computes the maximum of the two int32 args 129 | func Max32(a, b int32) int32 { 130 | if a > b { 131 | return a 132 | } 133 | return b 134 | } 135 | 136 | // Min32 computes the minimum of the two int32 args 137 | func Min32(a, b int32) int32 { 138 | if a < b { 139 | return a 140 | } 141 | return b 142 | } 143 | 144 | // Abs32 computes the absolute value of the given value 145 | func Abs32(a int32) int32 { 146 | if a < 0 { 147 | return -a 148 | } 149 | return a 150 | } 151 | 152 | // IntMultiple returns the interger multiple of mod that is always >= given value: 153 | // int(Ceil(val / mod)) * mod 154 | func IntMultiple(val, mod int) int { 155 | return int(math.Ceil(float64(val)/float64(mod))) * mod 156 | } 157 | -------------------------------------------------------------------------------- /ki/Makefile: -------------------------------------------------------------------------------- 1 | # Basic Go makefile 2 | 3 | GOCMD=go 4 | GOBUILD=$(GOCMD) build 5 | GOCLEAN=$(GOCMD) clean 6 | GOTEST=$(GOCMD) test 7 | GOGET=$(GOCMD) get 8 | 9 | 10 | all: build 11 | 12 | build: 13 | $(GOBUILD) -v 14 | test: 15 | $(GOTEST) -v ./... 16 | clean: 17 | $(GOCLEAN) 18 | 19 | # NOTE: MUST update version number here prior to running 'make release' 20 | VERS=v1.1.17 21 | PACKAGE=ki 22 | GIT_COMMIT=`git rev-parse --short HEAD` 23 | VERS_DATE=`date -u +%Y-%m-%d\ %H:%M` 24 | VERS_FILE=version.go 25 | 26 | release: 27 | /bin/rm -f $(VERS_FILE) 28 | @echo "// WARNING: auto-generated by Makefile release target -- run 'make release' to update" > $(VERS_FILE) 29 | @echo "" >> $(VERS_FILE) 30 | @echo "package $(PACKAGE)" >> $(VERS_FILE) 31 | @echo "" >> $(VERS_FILE) 32 | @echo "const (" >> $(VERS_FILE) 33 | @echo " Version = \"$(VERS)\"" >> $(VERS_FILE) 34 | @echo " GitCommit = \"$(GIT_COMMIT)\" // the commit JUST BEFORE the release" >> $(VERS_FILE) 35 | @echo " VersionDate = \"$(VERS_DATE)\" // UTC" >> $(VERS_FILE) 36 | @echo ")" >> $(VERS_FILE) 37 | @echo "" >> $(VERS_FILE) 38 | goimports -w $(VERS_FILE) 39 | /bin/cat $(VERS_FILE) 40 | git commit -am "$(VERS) release -- $(VERS_FILE) updated" 41 | git tag -a $(VERS) -m "$(VERS) release" 42 | git push 43 | git push origin --tags 44 | 45 | 46 | -------------------------------------------------------------------------------- /ki/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package ki provides the base element of GoKi Trees: Ki = Tree in Japanese, and 7 | "Key" in English -- powerful tree structures supporting scenegraphs, programs, 8 | parsing, etc. 9 | 10 | The Node struct that implements the Ki interface, which can be used as an 11 | embedded type (or a struct field) in other structs to provide core tree 12 | functionality, including: 13 | 14 | - Parent / Child Tree structure -- each Node can ONLY have one parent. 15 | Node struct's can also have Node fields -- these are functionally like 16 | fixed auto-named children. 17 | 18 | - Paths for locating Nodes within the hierarchy -- key for many use-cases, 19 | including ability to convert pointers to/from strings for IO and robust 20 | deep copy and move functions. The path separator is / for children and 21 | . for fields. 22 | 23 | - Apply a function across nodes up or down a tree (natural "me first", 24 | breadth-first, depth-first) -- very flexible for tree walking. 25 | 26 | - Generalized I/O -- can Save and Load the Tree as JSON, XML, etc -- 27 | including pointers which are saved using paths and automatically 28 | cached-out after loading -- enums also bidirectionally convertable to 29 | strings using enum type registry in kit package. 30 | 31 | - Robust deep copy, clone, move of nodes, with automatic pointer updating. 32 | 33 | - Signal sending and receiving between Nodes (simlar to Qt Signals / 34 | Slots) -- setup connections once and then emit signals to all receivers 35 | when relevant event happens. 36 | 37 | - Robust state updating -- wrap updates in UpdateStart / End, and signals 38 | are blocked until the final end, at the highest affected level in the 39 | tree, at which point a single update signal is sent -- automatically 40 | gives the minimal update. 41 | 42 | - Properties (as a string-keyed map) with property inheritance, including 43 | type-level properties via kit type registry. 44 | 45 | In general, the names of the children of a given node should all be unique. 46 | The following functions defined in ki package can be used: 47 | 48 | * UniqueNameCheck(node) to check for unique names on node if uncertain. 49 | * UniqueNameCheckAll(node) to check entire tree under given node. 50 | * UniquifyNames(node) to add a suffix to name to ensure uniqueness. 51 | * UniquifyNamesAll(node) to to uniquify all names in entire tree. 52 | 53 | The Ki interface is designed to support virtual method calling in Go 54 | and is only intended to be implemented once, by the ki.Node type 55 | (as opposed to interfaces that are used for hiding multiple different 56 | implementations of a common concept). Thus, all of the fields in ki.Node 57 | are exported (have captital names), to be accessed directly in types 58 | that embed and extend the ki.Node. The Ki interface has the "formal" name 59 | (e.g., Children) while the Node has the "nickname" (e.g., Kids). See the 60 | Naming Conventions on the GoKi Wiki for more details. 61 | 62 | Each Node stores the Ki interface version of itself, as This() / Ths 63 | which enables full virtual function calling by calling the method 64 | on that interface instead of directly on the receiver Node itself. 65 | This requires proper initialization via Init method of the Ki interface. 66 | */ 67 | package ki 68 | -------------------------------------------------------------------------------- /ki/flags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | import "github.com/goki/ki/kit" 8 | 9 | // Flags are bit flags for efficient core state of nodes -- see bitflag 10 | // package for using these ordinal values to manipulate bit flag field. 11 | type Flags int32 12 | 13 | //go:generate stringer -type=Flags 14 | 15 | var KiT_Flags = kit.Enums.AddEnum(FlagsN, kit.BitFlag, nil) 16 | 17 | const ( 18 | // IsField indicates a node is a field in its parent node, not a child in children. 19 | IsField Flags = iota 20 | 21 | // HasKiFields indicates a node has Ki Node fields that will be processed in recursive descent. 22 | // Use the HasFields() method to check as it will establish validity of flags on first call. 23 | // If neither HasFields nor HasNoFields are set, then it knows to update flags. 24 | HasKiFields 25 | 26 | // HasNoKiFields indicates a node has NO Ki Node fields that will be processed in recursive descent. 27 | // Use the HasFields() method to check as it will establish validity of flags on first call. 28 | // If neither HasFields nor HasNoFields are set, then it knows to update flags. 29 | HasNoKiFields 30 | 31 | // Updating flag is set at UpdateStart and cleared if we were the first 32 | // updater at UpdateEnd. 33 | Updating 34 | 35 | // OnlySelfUpdate means that the UpdateStart / End logic only applies to 36 | // this node in isolation, not to its children -- useful for a parent node 37 | // that has a different functional role than its children. 38 | OnlySelfUpdate 39 | 40 | // following flags record what happened to a given node since the last 41 | // Update signal -- they are cleared at first UpdateStart and valid after 42 | // UpdateEnd 43 | 44 | // NodeDeleted means this node has been deleted. 45 | NodeDeleted 46 | 47 | // NodeDestroyed means this node has been destroyed -- do not trigger any 48 | // more update signals on it. 49 | NodeDestroyed 50 | 51 | // ChildAdded means one or more new children were added to the node. 52 | ChildAdded 53 | 54 | // ChildDeleted means one or more children were deleted from the node. 55 | ChildDeleted 56 | 57 | // ChildrenDeleted means all children were deleted. 58 | ChildrenDeleted 59 | 60 | // ValUpdated means a value was updated (Field, Prop, any kind of value) 61 | ValUpdated 62 | 63 | // FlagsN is total number of flags used by base Ki Node -- can extend from 64 | // here up to 64 bits. 65 | FlagsN 66 | 67 | // ChildUpdateFlagsMask is a mask for all child updates. 68 | ChildUpdateFlagsMask = (1 << uint32(ChildAdded)) | (1 << uint32(ChildDeleted)) | (1 << uint32(ChildrenDeleted)) 69 | 70 | // StruUpdateFlagsMask is a mask for all structural changes update flags. 71 | StruUpdateFlagsMask = ChildUpdateFlagsMask | (1 << uint32(NodeDeleted)) 72 | 73 | // ValUpdateFlagsMask is a mask for all non-structural, value-only changes update flags. 74 | ValUpdateFlagsMask = (1 << uint32(ValUpdated)) 75 | 76 | // UpdateFlagsMask is a Mask for all the update flags -- destroyed is 77 | // excluded b/c otherwise it would get cleared. 78 | UpdateFlagsMask = StruUpdateFlagsMask | ValUpdateFlagsMask 79 | ) 80 | -------------------------------------------------------------------------------- /ki/flags_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Flags"; DO NOT EDIT. 2 | 3 | package ki 4 | 5 | import ( 6 | "errors" 7 | "strconv" 8 | ) 9 | 10 | func _() { 11 | // An "invalid array index" compiler error signifies that the constant values have changed. 12 | // Re-run the stringer command to generate them again. 13 | var x [1]struct{} 14 | _ = x[IsField-0] 15 | _ = x[HasKiFields-1] 16 | _ = x[HasNoKiFields-2] 17 | _ = x[Updating-3] 18 | _ = x[OnlySelfUpdate-4] 19 | _ = x[NodeDeleted-5] 20 | _ = x[NodeDestroyed-6] 21 | _ = x[ChildAdded-7] 22 | _ = x[ChildDeleted-8] 23 | _ = x[ChildrenDeleted-9] 24 | _ = x[ValUpdated-10] 25 | _ = x[FlagsN-11] 26 | } 27 | 28 | const _Flags_name = "IsFieldHasKiFieldsHasNoKiFieldsUpdatingOnlySelfUpdateNodeDeletedNodeDestroyedChildAddedChildDeletedChildrenDeletedValUpdatedFlagsN" 29 | 30 | var _Flags_index = [...]uint8{0, 7, 18, 31, 39, 53, 64, 77, 87, 99, 114, 124, 130} 31 | 32 | func (i Flags) String() string { 33 | if i < 0 || i >= Flags(len(_Flags_index)-1) { 34 | return "Flags(" + strconv.FormatInt(int64(i), 10) + ")" 35 | } 36 | return _Flags_name[_Flags_index[i]:_Flags_index[i+1]] 37 | } 38 | 39 | func (i *Flags) FromString(s string) error { 40 | for j := 0; j < len(_Flags_index)-1; j++ { 41 | if s == _Flags_name[_Flags_index[j]:_Flags_index[j+1]] { 42 | *i = Flags(j) 43 | return nil 44 | } 45 | } 46 | return errors.New("String: " + s + " is not a valid option for type: Flags") 47 | } 48 | 49 | var _Flags_descMap = map[Flags]string{ 50 | 0: `IsField indicates a node is a field in its parent node, not a child in children.`, 51 | 1: `HasKiFields indicates a node has Ki Node fields that will be processed in recursive descent. Use the HasFields() method to check as it will establish validity of flags on first call. If neither HasFields nor HasNoFields are set, then it knows to update flags.`, 52 | 2: `HasNoKiFields indicates a node has NO Ki Node fields that will be processed in recursive descent. Use the HasFields() method to check as it will establish validity of flags on first call. If neither HasFields nor HasNoFields are set, then it knows to update flags.`, 53 | 3: `Updating flag is set at UpdateStart and cleared if we were the first updater at UpdateEnd.`, 54 | 4: `OnlySelfUpdate means that the UpdateStart / End logic only applies to this node in isolation, not to its children -- useful for a parent node that has a different functional role than its children.`, 55 | 5: `NodeDeleted means this node has been deleted.`, 56 | 6: `NodeDestroyed means this node has been destroyed -- do not trigger any more update signals on it.`, 57 | 7: `ChildAdded means one or more new children were added to the node.`, 58 | 8: `ChildDeleted means one or more children were deleted from the node.`, 59 | 9: `ChildrenDeleted means all children were deleted.`, 60 | 10: `ValUpdated means a value was updated (Field, Prop, any kind of value)`, 61 | 11: `FlagsN is total number of flags used by base Ki Node -- can extend from here up to 64 bits.`, 62 | } 63 | 64 | func (i Flags) Desc() string { 65 | if str, ok := _Flags_descMap[i]; ok { 66 | return str 67 | } 68 | return "Flags(" + strconv.FormatInt(int64(i), 10) + ")" 69 | } 70 | -------------------------------------------------------------------------------- /ki/names.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | // Named consts for bool args 8 | const ( 9 | // Continue = true can be returned from tree iteration functions to continue 10 | // processing down the tree, as compared to Break = false which stops this branch. 11 | Continue = true 12 | 13 | // Break = false can be returned from tree iteration functions to stop processing 14 | // this branch of the tree. 15 | Break = false 16 | 17 | // Embeds is used for methods that look for children or parents of different types. 18 | // Passing this argument means to look for embedded types for matches. 19 | Embeds = true 20 | 21 | // NoEmbeds is used for methods that look for children or parents of different types. 22 | // Passing this argument means to NOT look for embedded types for matches. 23 | NoEmbeds = false 24 | 25 | // DestroyKids is used for Delete methods to indicate that deleted children 26 | // should be destroyed (else can be re-used somewhere else). 27 | DestroyKids = true 28 | 29 | // NoDestroyKids is used for Delete methods to indicate that deleted children 30 | // should NOT be destroyed, so they can be re-used somewhere else. 31 | NoDestroyKids = false 32 | 33 | // ShallowCopy is used for Props CopyFrom functions to indicate a shallow copy of 34 | // Props or PropSlice within Props (points to source props) 35 | ShallowCopy = true 36 | 37 | // DeepCopy is used for Props CopyFrom functions to indicate a deep copy of 38 | // Props or PropSlice within Props 39 | DeepCopy = true 40 | 41 | // Inherit is used for PropInherit to indicate that inherited properties 42 | // from parent objects should be checked as well. Otherwise not. 43 | Inherit = true 44 | 45 | // NoInherit is used for PropInherit to indicate that inherited properties 46 | // from parent objects should NOT be checked. 47 | NoInherit = false 48 | 49 | // TypeProps is used for PropInherit to indicate that properties 50 | // set on the type should be checked. 51 | TypeProps = true 52 | 53 | // NoTypeProps is used for PropInherit to indicate that properties 54 | // set on the type should NOT be checked. 55 | NoTypeProps = false 56 | 57 | // Indent is used for Write methods to indicate that indenting should be done. 58 | Indent = true 59 | 60 | // NoIndent is used for Write methods to indicate that indenting should NOT be done. 61 | NoIndent = false 62 | ) 63 | -------------------------------------------------------------------------------- /ki/nodesignals_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=NodeSignals"; DO NOT EDIT. 2 | 3 | package ki 4 | 5 | import ( 6 | "errors" 7 | "strconv" 8 | ) 9 | 10 | func _() { 11 | // An "invalid array index" compiler error signifies that the constant values have changed. 12 | // Re-run the stringer command to generate them again. 13 | var x [1]struct{} 14 | _ = x[NodeSignalNil-0] 15 | _ = x[NodeSignalUpdated-1] 16 | _ = x[NodeSignalDeleting-2] 17 | _ = x[NodeSignalsN-3] 18 | } 19 | 20 | const _NodeSignals_name = "NodeSignalNilNodeSignalUpdatedNodeSignalDeletingNodeSignalsN" 21 | 22 | var _NodeSignals_index = [...]uint8{0, 13, 30, 48, 60} 23 | 24 | func (i NodeSignals) String() string { 25 | if i < 0 || i >= NodeSignals(len(_NodeSignals_index)-1) { 26 | return "NodeSignals(" + strconv.FormatInt(int64(i), 10) + ")" 27 | } 28 | return _NodeSignals_name[_NodeSignals_index[i]:_NodeSignals_index[i+1]] 29 | } 30 | 31 | func (i *NodeSignals) FromString(s string) error { 32 | for j := 0; j < len(_NodeSignals_index)-1; j++ { 33 | if s == _NodeSignals_name[_NodeSignals_index[j]:_NodeSignals_index[j+1]] { 34 | *i = NodeSignals(j) 35 | return nil 36 | } 37 | } 38 | return errors.New("String: " + s + " is not a valid option for type: NodeSignals") 39 | } 40 | 41 | var _NodeSignals_descMap = map[NodeSignals]string{ 42 | 0: `NodeSignalNil is a nil signal value`, 43 | 1: `NodeSignalUpdated indicates that the node was updated -- the node Flags accumulate the specific changes made since the last update signal -- these flags are sent in the signal data -- strongly recommend using that instead of the flags, which can be subsequently updated by the time a signal is processed`, 44 | 2: `NodeSignalDeleting indicates that the node is being deleted from its parent children list -- this is not blocked by Updating status and is delivered immediately. No further notifications are sent -- assume it will be destroyed unless you hear from it again.`, 45 | 3: ``, 46 | } 47 | 48 | func (i NodeSignals) Desc() string { 49 | if str, ok := _NodeSignals_descMap[i]; ok { 50 | return str 51 | } 52 | return "NodeSignals(" + strconv.FormatInt(int64(i), 10) + ")" 53 | } 54 | -------------------------------------------------------------------------------- /ki/props.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "fmt" 11 | "log" 12 | "reflect" 13 | "strings" 14 | 15 | "github.com/goki/ki/kit" 16 | ) 17 | 18 | // Props is the type used for holding generic properties -- the actual Go type 19 | // is a mouthful and not very gui-friendly, and we need some special json methods 20 | type Props map[string]any 21 | 22 | var KiT_Props = kit.Types.AddType(&Props{}, PropsProps) 23 | 24 | var PropsProps = Props{ 25 | "basic-type": true, // registers props as a basic type avail for type selection in creating property values -- many cases call for nested properties 26 | } 27 | 28 | // Set sets props value -- safely creates map 29 | func (pr *Props) Set(key string, val any) { 30 | if *pr == nil { 31 | *pr = make(Props) 32 | } 33 | (*pr)[key] = val 34 | } 35 | 36 | // Prop returns property of given key 37 | func (pr Props) Prop(key string) any { 38 | return pr[key] 39 | } 40 | 41 | // Delete deletes props value at given key 42 | func (pr Props) Delete(key string) { 43 | delete(pr, key) 44 | } 45 | 46 | // SubProps returns a value that contains another props, or nil and false if 47 | // it doesn't exist or isn't a Props 48 | func SubProps(pr map[string]any, key string) (Props, bool) { 49 | sp, ok := pr[key] 50 | if !ok { 51 | return nil, false 52 | } 53 | spp, ok := sp.(Props) 54 | if ok { 55 | return spp, true 56 | } 57 | return nil, false 58 | } 59 | 60 | // SubTypeProps returns a value that contains another props, or nil and false if 61 | // it doesn't exist or isn't a Props -- for TypeProps, uses locking 62 | func SubTypeProps(pr map[string]any, key string) (Props, bool) { 63 | sp, ok := kit.TypeProp(pr, key) 64 | if !ok { 65 | return nil, false 66 | } 67 | spp, ok := sp.(Props) 68 | if ok { 69 | return spp, true 70 | } 71 | return nil, false 72 | } 73 | 74 | // SetPropStr is a convenience method for e.g., python wrapper that avoids need to deal 75 | // directly with props interface{} type 76 | func SetPropStr(pr Props, key, val string) { 77 | pr[key] = val 78 | } 79 | 80 | // SetSubProps is a convenience method for e.g., python wrapper that avoids need to deal 81 | // directly with props interface{} type 82 | func SetSubProps(pr Props, key string, sp Props) { 83 | pr[key] = sp 84 | } 85 | 86 | // special key prefix indicating type info 87 | var struTypeKey = "__type:" 88 | 89 | // special key prefix for enums 90 | var enumTypeKey = "__enum:" 91 | 92 | // BlankProp is an empty property, for when there isn't any need for the value 93 | type BlankProp struct{} 94 | 95 | // PropStruct is a struct of Name and Value, for use in a PropSlice to hold 96 | // properties that require order information (maps do not retain any order) 97 | type PropStruct struct { 98 | Name string 99 | Value any 100 | } 101 | 102 | // PropSlice is a slice of PropStruct, for when order is important within a 103 | // subset of properties (maps do not retain order) -- can set the value of a 104 | // property to a PropSlice to create an ordered list of property values. 105 | type PropSlice []PropStruct 106 | 107 | // ElemLabel satisfies the gi.SliceLabeler interface to provide labels for slice elements 108 | func (ps *PropSlice) ElemLabel(idx int) string { 109 | return (*ps)[idx].Name 110 | } 111 | 112 | // SliceProps returns a value that contains a PropSlice, or nil and false if it doesn't 113 | // exist or isn't a PropSlice 114 | func SliceProps(pr map[string]any, key string) (PropSlice, bool) { 115 | sp, ok := pr[key] 116 | if !ok { 117 | return nil, false 118 | } 119 | spp, ok := sp.(PropSlice) 120 | if ok { 121 | return spp, true 122 | } 123 | return nil, false 124 | } 125 | 126 | // SliceTypeProps returns a value that contains a PropSlice, or nil and false if it doesn't 127 | // exist or isn't a PropSlice -- for TypeProps, uses locking 128 | func SliceTypeProps(pr map[string]any, key string) (PropSlice, bool) { 129 | sp, ok := kit.TypeProp(pr, key) 130 | if !ok { 131 | return nil, false 132 | } 133 | spp, ok := sp.(PropSlice) 134 | if ok { 135 | return spp, true 136 | } 137 | return nil, false 138 | } 139 | 140 | // CopyProps copies properties from source to destination map. If deepCopy 141 | // is true, then any values that are Props or PropSlice are copied too 142 | // *dest can be nil, in which case it is created. 143 | func CopyProps(dest *map[string]any, src map[string]any, deepCopy bool) { 144 | if *dest == nil { 145 | *dest = make(Props, len(src)) 146 | } 147 | for key, val := range src { 148 | if deepCopy { 149 | if pv, ok := val.(map[string]any); ok { 150 | var nval Props 151 | nval.CopyFrom(pv, deepCopy) 152 | (*dest)[key] = nval 153 | continue 154 | } else if pv, ok := val.(Props); ok { 155 | var nval Props 156 | nval.CopyFrom(pv, deepCopy) 157 | (*dest)[key] = nval 158 | continue 159 | } else if pv, ok := val.(PropSlice); ok { 160 | var nval PropSlice 161 | nval.CopyFrom(pv, deepCopy) 162 | (*dest)[key] = nval 163 | continue 164 | } 165 | } 166 | (*dest)[key] = val 167 | } 168 | } 169 | 170 | // CopyFrom copies properties from source to receiver destination map. If deepCopy 171 | // is true, then any values that are Props or PropSlice are copied too 172 | // *dest can be nil, in which case it is created. 173 | func (dest *Props) CopyFrom(src map[string]any, deepCopy bool) { 174 | CopyProps((*map[string]any)(dest), src, deepCopy) 175 | } 176 | 177 | // CopyFrom copies properties from source to destination propslice. If deepCopy 178 | // is true, then any values that are Props or PropSlice are copied too 179 | // *dest can be nil, in which case it is created. 180 | func (dest *PropSlice) CopyFrom(src PropSlice, deepCopy bool) { 181 | if *dest == nil { 182 | *dest = make(PropSlice, len(src)) 183 | } 184 | for i, val := range src { 185 | if deepCopy { 186 | if pv, ok := val.Value.(map[string]any); ok { 187 | var nval Props 188 | CopyProps((*map[string]any)(&nval), pv, deepCopy) 189 | (*dest)[i] = PropStruct{Name: val.Name, Value: nval} 190 | continue 191 | } else if pv, ok := val.Value.(Props); ok { 192 | var nval Props 193 | CopyProps((*map[string]any)(&nval), pv, deepCopy) 194 | (*dest)[i] = PropStruct{Name: val.Name, Value: nval} 195 | continue 196 | } else if pv, ok := val.Value.(PropSlice); ok { 197 | var nval PropSlice 198 | nval.CopyFrom(pv, deepCopy) 199 | (*dest)[i] = PropStruct{Name: val.Name, Value: nval} 200 | continue 201 | } 202 | } 203 | (*dest)[i] = src[i] 204 | } 205 | } 206 | 207 | // MarshalJSON saves the type information for each struct used in props, as a 208 | // separate key with the __type: prefix -- this allows the Unmarshal to 209 | // create actual types 210 | func (p Props) MarshalJSON() ([]byte, error) { 211 | nk := len(p) 212 | b := make([]byte, 0, nk*100+20) 213 | if nk == 0 { 214 | b = append(b, []byte("null")...) 215 | return b, nil 216 | } 217 | b = append(b, []byte("{")...) 218 | cnt := 0 219 | var err error 220 | for key, val := range p { 221 | vt := kit.NonPtrType(reflect.TypeOf(val)) 222 | vk := vt.Kind() 223 | if vk == reflect.Struct { 224 | knm := kit.Types.TypeName(vt) 225 | tstr := fmt.Sprintf("\"%v%v\": \"%v\",", struTypeKey, key, knm) 226 | b = append(b, []byte(tstr)...) 227 | } 228 | kstr := fmt.Sprintf("\"%v\": ", key) 229 | b = append(b, []byte(kstr)...) 230 | 231 | var kb []byte 232 | kb, err = json.Marshal(val) 233 | if err != nil { 234 | log.Printf("error doing json.Marshall from val: %v\n%v\n", val, err) 235 | log.Printf("output to point of error: %v\n", string(b)) 236 | } else { 237 | if vk >= reflect.Int && vk <= reflect.Uint64 && kit.Enums.TypeRegistered(vt) { 238 | knm := kit.Types.TypeName(vt) 239 | kb, _ = json.Marshal(val) 240 | estr := fmt.Sprintf("\"%v(%v)%v\"", enumTypeKey, knm, string(bytes.Trim(kb, "\""))) 241 | b = append(b, []byte(estr)...) 242 | } else { 243 | b = append(b, kb...) 244 | } 245 | } 246 | if cnt < nk-1 { 247 | b = append(b, []byte(",")...) 248 | } 249 | cnt++ 250 | } 251 | b = append(b, []byte("}")...) 252 | // fmt.Printf("json out: %v\n", string(b)) 253 | return b, nil 254 | } 255 | 256 | // UnmarshalJSON parses the type information in the map to restore actual 257 | // objects -- this is super inefficient and really needs a native parser, but 258 | // props are likely to be relatively small 259 | func (p *Props) UnmarshalJSON(b []byte) error { 260 | // fmt.Printf("json in: %v\n", string(b)) 261 | if bytes.Equal(b, []byte("null")) { 262 | *p = nil 263 | return nil 264 | } 265 | 266 | // load into a temporary map and then process 267 | tmp := make(map[string]any) 268 | err := json.Unmarshal(b, &tmp) 269 | if err != nil { 270 | return err 271 | } 272 | 273 | *p = make(Props, len(tmp)) 274 | 275 | // create all the structure objects from the list -- have to do this first to get all 276 | // the structs made b/c the order is random.. 277 | for key, val := range tmp { 278 | if strings.HasPrefix(key, struTypeKey) { 279 | pkey := strings.TrimPrefix(key, struTypeKey) 280 | rval := tmp[pkey] 281 | tn := val.(string) 282 | typ := kit.Types.Type(tn) 283 | if typ == nil { 284 | log.Printf("ki.Props: cannot load struct of type %v -- not registered in kit.Types\n", tn) 285 | continue 286 | } 287 | if IsKi(typ) { // note: not really a good idea to store ki's in maps, but.. 288 | kival := NewOfType(typ) 289 | InitNode(kival) 290 | if kival != nil { 291 | // fmt.Printf("stored new ki of type %v in key: %v\n", typ.String(), pkey) 292 | tmpb, _ := json.Marshal(rval) // string rep of this 293 | err = kival.ReadJSON(bytes.NewReader(tmpb)) 294 | if err != nil { 295 | log.Printf("ki.Props failed to load Ki struct of type %v with error: %v\n", typ.String(), err) 296 | } 297 | (*p)[pkey] = kival 298 | } 299 | } else { 300 | stval := reflect.New(typ).Interface() 301 | // fmt.Printf("stored new struct of type %v in key: %v\n", typ.String(), pkey) 302 | tmpb, _ := json.Marshal(rval) // string rep of this 303 | err = json.Unmarshal(tmpb, stval) 304 | if err != nil { 305 | log.Printf("ki.Props failed to load struct of type %v with error: %v\n", typ.String(), err) 306 | } 307 | (*p)[pkey] = reflect.ValueOf(stval).Elem().Interface() 308 | } 309 | } 310 | } 311 | 312 | // now can re-iterate 313 | for key, val := range tmp { 314 | if strings.HasPrefix(key, struTypeKey) { 315 | continue 316 | } 317 | if _, ok := (*p)[key]; ok { // already created -- was a struct -- skip 318 | continue 319 | } 320 | // look for sub-maps, make them props.. 321 | if _, ok := val.(map[string]any); ok { 322 | // fmt.Printf("stored new Props map in key: %v\n", key) 323 | subp := Props{} 324 | tmpb, _ := json.Marshal(val) // string rep of this 325 | err = json.Unmarshal(tmpb, &subp) 326 | if err != nil { 327 | log.Printf("ki.Props failed to load sub-Props with error: %v\n", err) 328 | } 329 | (*p)[key] = subp 330 | } else { // straight copy 331 | if sval, ok := val.(string); ok { 332 | if strings.HasPrefix(sval, enumTypeKey) { 333 | tn := strings.TrimPrefix(sval, enumTypeKey) 334 | rpi := strings.Index(tn, ")") 335 | str := tn[rpi+1:] 336 | tn = tn[1:rpi] 337 | etyp := kit.Enums.Enum(tn) 338 | if etyp != nil { 339 | eval := kit.EnumIfaceFromString(str, etyp) 340 | (*p)[key] = eval 341 | // fmt.Printf("decoded enum typ %v into actual value: %v from %v\n", etyp.String(), eval, str) 342 | } else { 343 | (*p)[key] = str 344 | } 345 | continue 346 | } 347 | } 348 | (*p)[key] = val 349 | } 350 | } 351 | 352 | return nil 353 | } 354 | -------------------------------------------------------------------------------- /ki/props_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | import ( 8 | "encoding/json" 9 | "testing" 10 | 11 | "github.com/goki/ki/kit" 12 | ) 13 | 14 | var PropsTest = Props{ 15 | "intprop": -17, 16 | "floatprop": 3.1415, 17 | "stringprop": "type string", 18 | "#subprops": Props{ 19 | "sp1": "#FFE", 20 | "sp2": 42.2, 21 | }, 22 | "subtype": NodeEmbed{ 23 | Mbr1: "smbr", 24 | Mbr2: 17, 25 | }, 26 | "testenum": kit.TestFlag2, 27 | } 28 | 29 | func TestPropsJSonSave(t *testing.T) { 30 | b, err := json.MarshalIndent(PropsTest, "", " ") 31 | if err != nil { 32 | t.Error(err) 33 | // } else { 34 | // fmt.Printf("props json output:\n%v\n", string(b)) 35 | } 36 | 37 | tstload := make(Props) 38 | err = json.Unmarshal(b, &tstload) 39 | if err != nil { 40 | t.Error(err) 41 | // } else { 42 | // // tstb, _ := json.MarshalIndent(tstload, "", " ") 43 | // fmt.Printf("props test loaded json output:\n%v\n", string(tstb)) 44 | // because of the map randomization, this is not testable, do it manually.. 45 | // if !bytes.Equal(tstb, b) { 46 | // t.Error("props original and unmarshal'd json rep are not equivalent") 47 | // } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ki/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | import ( 8 | "fmt" 9 | "sync" 10 | 11 | "github.com/goki/ki/kit" 12 | ) 13 | 14 | // note: Started this code based on: github.com/tucnak/meta/ 15 | 16 | // NodeSignals are signals that a Ki node sends about updates to the tree 17 | // structure using the NodeSignal (convert sig int64 to NodeSignals to get the 18 | // stringer name). 19 | type NodeSignals int64 20 | 21 | // Standard signal types sent by ki.Node on its NodeSig for tree state changes 22 | const ( 23 | // NodeSignalNil is a nil signal value 24 | NodeSignalNil NodeSignals = iota 25 | 26 | // NodeSignalUpdated indicates that the node was updated -- the node Flags 27 | // accumulate the specific changes made since the last update signal -- 28 | // these flags are sent in the signal data -- strongly recommend using 29 | // that instead of the flags, which can be subsequently updated by the 30 | // time a signal is processed 31 | NodeSignalUpdated 32 | 33 | // NodeSignalDeleting indicates that the node is being deleted from its 34 | // parent children list -- this is not blocked by Updating status and is 35 | // delivered immediately. No further notifications are sent -- assume 36 | // it will be destroyed unless you hear from it again. 37 | NodeSignalDeleting 38 | 39 | NodeSignalsN 40 | ) 41 | 42 | //go:generate stringer -type=NodeSignals 43 | 44 | // SignalTrace can be set to true to automatically print out a trace of the 45 | // signals as they are sent 46 | var SignalTrace bool = false 47 | 48 | // SignalTraceString can be set to a string that will then accumulate the 49 | // trace of signals sent, for use in testing -- otherwise the trace just goes 50 | // to stdout 51 | var SignalTraceString *string 52 | 53 | // RecvFunc is a receiver function type for signals -- gets the full 54 | // connection information and signal, data as specified by the sender. It is 55 | // good practice to avoid closures in these functions, which can be numerous 56 | // and have a long lifetime, by converting the recv, send into their known 57 | // types and referring to them directly 58 | type RecvFunc func(recv, send Ki, sig int64, data any) 59 | 60 | // Signal implements general signal passing between Ki objects, like Qt's 61 | // Signal / Slot system. 62 | // 63 | // This design pattern separates three different factors: 64 | // * when to signal that something has happened 65 | // * who should receive that signal 66 | // * what should the receiver do in response to the signal 67 | // 68 | // Keeping these three things entirely separate greatly simplifies the overall 69 | // logic. 70 | // 71 | // A receiver connects in advance to a given signal on a sender to receive its 72 | // signals -- these connections are typically established in an initialization 73 | // step. There can be multiple different signals on a given sender, and to 74 | // make more efficient use of signal connections, the sender can also send an 75 | // int64 signal value that further discriminates the nature of the event, as 76 | // documented in the code associated with the sender (typically an enum is 77 | // used). Furthermore, arbitrary data as an interface{} can be passed as 78 | // well. 79 | // 80 | // The Signal uses a map indexed by the receiver pointer to hold the 81 | // connections -- this means that there can only be one such connection per 82 | // receiver, and the order of signal emission to different receivers will be random. 83 | // 84 | // Typically an inline anonymous closure receiver function is used to keep all 85 | // the relevant code in one place. Due to the typically long-standing nature 86 | // of these connections, it is more efficient to avoid capturing external 87 | // variables, and rely instead on appropriately interpreting the sent argument 88 | // values. e.g.: 89 | // 90 | // send := sender.EmbeddedStruct(KiT_SendType).(*SendType) 91 | // 92 | // is guaranteed to result in a usable pointer to the sender of known type at 93 | // least SendType, in a case where that sender might actually embed that 94 | // SendType (otherwise if it is known to be of a given type, just directly 95 | // converting as such is fine) 96 | type Signal struct { 97 | 98 | // [view: -] map of receivers and their functions 99 | Cons map[Ki]RecvFunc `view:"-" json:"-" xml:"-" desc:"map of receivers and their functions"` 100 | 101 | // [view: -] read-write mutex that protects Cons map access -- use RLock for all Cons reads, Lock for all writes 102 | Mu sync.RWMutex `view:"-" json:"-" xml:"-" desc:"read-write mutex that protects Cons map access -- use RLock for all Cons reads, Lock for all writes"` 103 | } 104 | 105 | var KiT_Signal = kit.Types.AddType(&Signal{}, nil) 106 | 107 | // ConnectOnly first deletes any existing connections and then attaches a new 108 | // receiver to the signal 109 | func (s *Signal) ConnectOnly(recv Ki, fun RecvFunc) { 110 | s.DisconnectAll() 111 | s.Connect(recv, fun) 112 | } 113 | 114 | // Connect attaches a new receiver and function to the signal -- only one such 115 | // connection per receiver can be made, so any existing connection to that 116 | // receiver will be overwritten 117 | func (s *Signal) Connect(recv Ki, fun RecvFunc) { 118 | s.Mu.Lock() 119 | if s.Cons == nil { 120 | s.Cons = make(map[Ki]RecvFunc) 121 | } 122 | s.Cons[recv] = fun 123 | s.Mu.Unlock() 124 | } 125 | 126 | // Disconnect disconnects (deletes) the connection for a given receiver 127 | func (s *Signal) Disconnect(recv Ki) { 128 | s.Mu.Lock() 129 | delete(s.Cons, recv) 130 | s.Mu.Unlock() 131 | } 132 | 133 | // DisconnectDestroyed disconnects (deletes) the connection for a given receiver, 134 | // if receiver is destroyed, assumed to be under an RLock (unlocks, relocks read lock). 135 | // Returns true if was disconnected. 136 | func (s *Signal) DisconnectDestroyed(recv Ki) bool { 137 | if recv.IsDestroyed() { 138 | s.Mu.RUnlock() 139 | s.Disconnect(recv) 140 | s.Mu.RLock() 141 | return true 142 | } 143 | return false 144 | } 145 | 146 | // DisconnectAll removes all connections 147 | func (s *Signal) DisconnectAll() { 148 | s.Mu.Lock() 149 | s.Cons = make(map[Ki]RecvFunc) 150 | s.Mu.Unlock() 151 | } 152 | 153 | // EmitTrace records a trace of signal being emitted 154 | func (s *Signal) EmitTrace(sender Ki, sig int64, data any) { 155 | if SignalTraceString != nil { 156 | *SignalTraceString += fmt.Sprintf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Name(), NodeSignals(sig), data) 157 | } else { 158 | fmt.Printf("ki.Signal Emit from: %v sig: %v data: %v\n", sender.Path(), NodeSignals(sig), data) 159 | } 160 | } 161 | 162 | // Emit sends the signal across all the connections to the receivers -- 163 | // sequentially but in random order due to the randomization of map iteration 164 | func (s *Signal) Emit(sender Ki, sig int64, data any) { 165 | if sender == nil || sender.IsDestroyed() { // dead nodes don't talk.. 166 | return 167 | } 168 | if SignalTrace { 169 | s.EmitTrace(sender, sig, data) 170 | } 171 | s.Mu.RLock() 172 | for recv, fun := range s.Cons { 173 | if s.DisconnectDestroyed(recv) { 174 | continue 175 | } 176 | s.Mu.RUnlock() 177 | fun(recv, sender, sig, data) 178 | s.Mu.RLock() 179 | } 180 | s.Mu.RUnlock() 181 | } 182 | 183 | // EmitGo is the concurrent version of Emit -- sends the signal across all the 184 | // connections to the receivers as separate goroutines 185 | func (s *Signal) EmitGo(sender Ki, sig int64, data any) { 186 | if sender == nil || sender.IsDestroyed() { // dead nodes don't talk.. 187 | return 188 | } 189 | if SignalTrace { 190 | s.EmitTrace(sender, sig, data) 191 | } 192 | s.Mu.RLock() 193 | for recv, fun := range s.Cons { 194 | if s.DisconnectDestroyed(recv) { 195 | continue 196 | } 197 | s.Mu.RUnlock() 198 | go fun(recv, sender, sig, data) 199 | s.Mu.RLock() 200 | } 201 | s.Mu.RUnlock() 202 | } 203 | 204 | // SignalFilterFunc is the function type for filtering signals before they are 205 | // sent -- returns false to prevent sending, and true to allow sending 206 | type SignalFilterFunc func(recv Ki) bool 207 | 208 | // EmitFiltered calls function on each potential receiver, and only sends 209 | // signal if function returns true 210 | func (s *Signal) EmitFiltered(sender Ki, sig int64, data any, filtFun SignalFilterFunc) { 211 | s.Mu.RLock() 212 | for recv, fun := range s.Cons { 213 | if s.DisconnectDestroyed(recv) { 214 | continue 215 | } 216 | s.Mu.RUnlock() 217 | if filtFun(recv) { 218 | fun(recv, sender, sig, data) 219 | } 220 | s.Mu.RLock() 221 | } 222 | s.Mu.RUnlock() 223 | } 224 | 225 | // EmitGoFiltered is the concurrent version of EmitFiltered -- calls function 226 | // on each potential receiver, and only sends signal if function returns true 227 | // (filtering is sequential iteration over receivers) 228 | func (s *Signal) EmitGoFiltered(sender Ki, sig int64, data any, filtFun SignalFilterFunc) { 229 | s.Mu.RLock() 230 | for recv, fun := range s.Cons { 231 | if s.DisconnectDestroyed(recv) { 232 | continue 233 | } 234 | s.Mu.RUnlock() 235 | if filtFun(recv) { 236 | go fun(recv, sender, sig, data) 237 | } 238 | s.Mu.RLock() 239 | } 240 | s.Mu.RUnlock() 241 | } 242 | 243 | // ConsFunc iterates over the connections with read lock and deletion of 244 | // destroyed objects, calling given function on each connection -- if 245 | // it returns false, then iteration is stopped, else continues. 246 | // function is called with no lock in place. 247 | func (s *Signal) ConsFunc(consFun func(recv Ki, fun RecvFunc) bool) { 248 | s.Mu.RLock() 249 | for recv, fun := range s.Cons { 250 | if s.DisconnectDestroyed(recv) { 251 | continue 252 | } 253 | s.Mu.RUnlock() 254 | if !consFun(recv, fun) { 255 | s.Mu.RLock() 256 | break 257 | } 258 | s.Mu.RLock() 259 | } 260 | s.Mu.RUnlock() 261 | } 262 | 263 | // SendSig sends a signal to one given receiver -- receiver must already be 264 | // connected so that its receiving function is available 265 | func (s *Signal) SendSig(recv, sender Ki, sig int64, data any) { 266 | s.Mu.RLock() 267 | fun := s.Cons[recv] 268 | s.Mu.RUnlock() 269 | if fun != nil { 270 | fun(recv, sender, sig, data) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /ki/signal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/goki/ki/kit" 13 | ) 14 | 15 | type TestNode struct { 16 | Node 17 | sig1 Signal 18 | sig2 Signal 19 | } 20 | 21 | var KiTTestNode = kit.Types.AddType(&TestNode{}, nil) 22 | 23 | func TestSignalConnect(t *testing.T) { 24 | parent := TestNode{} 25 | parent.InitName(&parent, "par1") 26 | typ := reflect.TypeOf(parent) 27 | child1 := parent.AddNewChild(typ, "child1") 28 | // child2 := parent.AddNewChild(nil, "child2") 29 | 30 | // note: now that signal is a map, cannot test reliably due to ordering 31 | res := make([]string, 0, 10) 32 | parent.sig1.Connect(child1, func(receiver, sender Ki, sig int64, data any) { 33 | res = append(res, fmt.Sprintf("recv: %v, sender: %v sig: %v data: %v", 34 | receiver.Name(), sender.Name(), NodeSignals(sig), data)) 35 | }) 36 | // parent.sig1.Connect(child2, func(receiver, sender Ki, sig int64, data interface{}) { 37 | // res = append(res, fmt.Sprintf("recv: %v, sender: %v sig: %v data: %v", 38 | // receiver.Name(), sender.Name(), NodeSignals(sig), data)) 39 | // }) 40 | 41 | parent.sig1.Emit(&parent, int64(NodeSignalNil), 1234) 42 | 43 | // fmt.Printf("res: %v\n", res) 44 | trg := []string{"recv: child1, sender: par1 sig: NodeSignalNil data: 1234"} 45 | if !reflect.DeepEqual(res, trg) { 46 | t.Errorf("Add child sigs error -- results: %v != target: %v\n", res, trg) 47 | } 48 | res = res[:0] 49 | 50 | // time.Sleep(time.Second * 2) 51 | } 52 | 53 | func TestSignalNameToInt(t *testing.T) { 54 | for i := NodeSignalNil; i < NodeSignalsN; i++ { 55 | st := NodeSignals(i) 56 | str := st.String() 57 | stc := NodeSignalNil 58 | err := stc.FromString(str) 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | stnm := stc.String() 63 | if stnm != str { 64 | t.Errorf("could not convert from signal type name %v -- got: %v -- maybe need to run go generate?", str, stnm) 65 | } 66 | } 67 | 68 | str := "NodeSignalUpdated" 69 | stc := NodeSignalNil 70 | stc.FromString(str) 71 | if stc.String() != str { 72 | t.Errorf("could not convert from signal type name %v -- got: %v -- maybe need to run go generate?", str, stc.String()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ki/slice.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package ki 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | 11 | "github.com/goki/ki/kit" 12 | ) 13 | 14 | // Slice is just a slice of ki elements: []Ki, providing methods for accessing 15 | // elements in the slice, and JSON marshal / unmarshal with encoding of 16 | // underlying types 17 | type Slice []Ki 18 | 19 | // StartMiddle indicates to start searching 20 | // in the middle for slice search functions. 21 | const StartMiddle int = -1 22 | 23 | // NOTE: we have to define Slice* functions operating on a generic *[]Ki 24 | // element as the first (not receiver) argument, to be able to use these 25 | // functions in any other types that are based on ki.Slice or are other forms 26 | // of []Ki. It doesn't seem like it would have been THAT hard to just grab 27 | // all the methods on Slice when you "inherit" from it -- unlike with structs, 28 | // where there are issues with the underlying representation, a simple "type A 29 | // B" kind of expression could easily have inherited the exact same code 30 | // because, underneath, it IS the same type. Only for the receiver methods -- 31 | // it does seem reasonable that other uses of different types should 32 | // differentiate them. But there you still be able to directly cast! 33 | 34 | // SliceIsValidIndex checks whether the given index is a valid index into slice, 35 | // within range of 0..len-1. Returns error if not. 36 | func SliceIsValidIndex(sl *[]Ki, idx int) error { 37 | if idx >= 0 && idx < len(*sl) { 38 | return nil 39 | } 40 | return fmt.Errorf("ki.Slice: invalid index: %v -- len = %v", idx, len(*sl)) 41 | } 42 | 43 | // IsValidIndex checks whether the given index is a valid index into slice, 44 | // within range of 0..len-1. Returns error if not. 45 | func (sl *Slice) IsValidIndex(idx int) error { 46 | if idx >= 0 && idx < len(*sl) { 47 | return nil 48 | } 49 | return fmt.Errorf("ki.Slice: invalid index: %v -- len = %v", idx, len(*sl)) 50 | } 51 | 52 | // Elem returns element at index -- panics if index is invalid 53 | func (sl *Slice) Elem(idx int) Ki { 54 | return (*sl)[idx] 55 | } 56 | 57 | // ElemTry returns element at index -- Try version returns error if index is invalid. 58 | func (sl *Slice) ElemTry(idx int) (Ki, error) { 59 | if err := sl.IsValidIndex(idx); err != nil { 60 | return nil, err 61 | } 62 | return (*sl)[idx], nil 63 | } 64 | 65 | // ElemFromEnd returns element at index from end of slice (0 = last element, 66 | // 1 = 2nd to last, etc). Panics if invalid index. 67 | func (sl *Slice) ElemFromEnd(idx int) Ki { 68 | return (*sl)[len(*sl)-1-idx] 69 | } 70 | 71 | // ElemFromEndTry returns element at index from end of slice (0 = last element, 72 | // 1 = 2nd to last, etc). Try version returns error on invalid index. 73 | func (sl *Slice) ElemFromEndTry(idx int) (Ki, error) { 74 | return sl.ElemTry(len(*sl) - 1 - idx) 75 | } 76 | 77 | // SliceIndexByFunc finds index of item based on match function (which must 78 | // return true for a find match, false for not). Returns false if not found. 79 | // startIdx arg allows for optimized bidirectional find if you have an idea 80 | // where it might be -- can be key speedup for large lists -- pass [ki.StartMiddle] to start 81 | // in the middle (good default) 82 | func SliceIndexByFunc(sl *[]Ki, startIdx int, match func(k Ki) bool) (int, bool) { 83 | sz := len(*sl) 84 | if sz == 0 { 85 | return -1, false 86 | } 87 | if startIdx < 0 { 88 | startIdx = sz / 2 89 | } 90 | if startIdx == 0 { 91 | for idx, child := range *sl { 92 | if match(child) { 93 | return idx, true 94 | } 95 | } 96 | } else { 97 | if startIdx >= sz { 98 | startIdx = sz - 1 99 | } 100 | upi := startIdx + 1 101 | dni := startIdx 102 | upo := false 103 | for { 104 | if !upo && upi < sz { 105 | if match((*sl)[upi]) { 106 | return upi, true 107 | } 108 | upi++ 109 | } else { 110 | upo = true 111 | } 112 | if dni >= 0 { 113 | if match((*sl)[dni]) { 114 | return dni, true 115 | } 116 | dni-- 117 | } else if upo { 118 | break 119 | } 120 | } 121 | } 122 | return -1, false 123 | } 124 | 125 | // IndexByFunc finds index of item based on match function (which must return 126 | // true for a find match, false for not). Returns false if not found. 127 | // startIdx arg allows for optimized bidirectional find if you have an idea 128 | // where it might be -- can be key speedup for large lists -- pass [ki.StartMiddle] to start 129 | // in the middle (good default). 130 | func (sl *Slice) IndexByFunc(startIdx int, match func(k Ki) bool) (int, bool) { 131 | return SliceIndexByFunc((*[]Ki)(sl), startIdx, match) 132 | } 133 | 134 | // SliceIndexOf returns index of element in list, false if not there. startIdx arg 135 | // allows for optimized bidirectional find if you have an idea where it might 136 | // be -- can be key speedup for large lists -- pass [ki.StartMiddle] to start in the middle 137 | // (good default). 138 | func SliceIndexOf(sl *[]Ki, kid Ki, startIdx int) (int, bool) { 139 | return SliceIndexByFunc(sl, startIdx, func(ch Ki) bool { return ch == kid }) 140 | } 141 | 142 | // IndexOf returns index of element in list, false if not there. startIdx arg 143 | // allows for optimized bidirectional find if you have an idea where it might 144 | // be -- can be key speedup for large lists -- pass [ki.StartMiddle] to start in the middle 145 | // (good default). 146 | func (sl *Slice) IndexOf(kid Ki, startIdx int) (int, bool) { 147 | return sl.IndexByFunc(startIdx, func(ch Ki) bool { return ch == kid }) 148 | } 149 | 150 | // SliceIndexByName returns index of first element that has given name, false if 151 | // not found. See IndexOf for info on startIdx. 152 | func SliceIndexByName(sl *[]Ki, name string, startIdx int) (int, bool) { 153 | return SliceIndexByFunc(sl, startIdx, func(ch Ki) bool { return ch.Name() == name }) 154 | } 155 | 156 | // IndexByName returns index of first element that has given name, false if 157 | // not found. See IndexOf for info on startIdx 158 | func (sl *Slice) IndexByName(name string, startIdx int) (int, bool) { 159 | return sl.IndexByFunc(startIdx, func(ch Ki) bool { return ch.Name() == name }) 160 | } 161 | 162 | // SliceIndexByType returns index of element that either is that type or embeds 163 | // that type, false if not found. See IndexOf for info on startIdx. 164 | func SliceIndexByType(sl *[]Ki, t reflect.Type, embeds bool, startIdx int) (int, bool) { 165 | if embeds { 166 | return SliceIndexByFunc(sl, startIdx, func(ch Ki) bool { return TypeEmbeds(ch, t) }) 167 | } 168 | return SliceIndexByFunc(sl, startIdx, func(ch Ki) bool { return Type(ch) == t }) 169 | } 170 | 171 | // IndexByType returns index of element that either is that type or embeds 172 | // that type, false if not found. See IndexOf for info on startIdx. 173 | func (sl *Slice) IndexByType(t reflect.Type, embeds bool, startIdx int) (int, bool) { 174 | if embeds { 175 | return sl.IndexByFunc(startIdx, func(ch Ki) bool { return TypeEmbeds(ch, t) }) 176 | } 177 | return sl.IndexByFunc(startIdx, func(ch Ki) bool { return Type(ch) == t }) 178 | } 179 | 180 | // ElemByName returns first element that has given name, nil if not found. 181 | // See IndexOf for info on startIdx. 182 | func (sl *Slice) ElemByName(name string, startIdx int) Ki { 183 | idx, ok := sl.IndexByName(name, startIdx) 184 | if !ok { 185 | return nil 186 | } 187 | return (*sl)[idx] 188 | } 189 | 190 | // ElemByNameTry returns first element that has given name, error if not found. 191 | // See IndexOf for info on startIdx. 192 | func (sl *Slice) ElemByNameTry(name string, startIdx int) (Ki, error) { 193 | idx, ok := sl.IndexByName(name, startIdx) 194 | if !ok { 195 | return nil, fmt.Errorf("ki.Slice: element named: %v not found", name) 196 | } 197 | return (*sl)[idx], nil 198 | } 199 | 200 | // ElemByType returns index of element that either is that type or embeds 201 | // that type, nil if not found. See IndexOf for info on startIdx. 202 | func (sl *Slice) ElemByType(t reflect.Type, embeds bool, startIdx int) Ki { 203 | idx, ok := sl.IndexByType(t, embeds, startIdx) 204 | if !ok { 205 | return nil 206 | } 207 | return (*sl)[idx] 208 | } 209 | 210 | // ElemByTypeTry returns index of element that either is that type or embeds 211 | // that type, error if not found. See IndexOf for info on startIdx. 212 | func (sl *Slice) ElemByTypeTry(t reflect.Type, embeds bool, startIdx int) (Ki, error) { 213 | idx, ok := sl.IndexByType(t, embeds, startIdx) 214 | if !ok { 215 | return nil, fmt.Errorf("ki.Slice: element of type: %v not found", t) 216 | } 217 | return (*sl)[idx], nil 218 | } 219 | 220 | // SliceInsert item at index -- does not do any parent updating etc -- use Ki/Node 221 | // method unless you know what you are doing. 222 | func SliceInsert(sl *[]Ki, k Ki, idx int) { 223 | kl := len(*sl) 224 | if idx < 0 { 225 | idx = kl + idx 226 | } 227 | if idx < 0 { // still? 228 | idx = 0 229 | } 230 | if idx > kl { // last position allowed for insert 231 | idx = kl 232 | } 233 | // this avoids extra garbage collection 234 | *sl = append(*sl, nil) 235 | if idx < kl { 236 | copy((*sl)[idx+1:], (*sl)[idx:kl]) 237 | } 238 | (*sl)[idx] = k 239 | } 240 | 241 | // Insert item at index -- does not do any parent updating etc -- use Ki/Node 242 | // method unless you know what you are doing. 243 | func (sl *Slice) Insert(k Ki, idx int) { 244 | SliceInsert((*[]Ki)(sl), k, idx) 245 | } 246 | 247 | // SliceDeleteAtIndex deletes item at index -- does not do any further management 248 | // deleted item -- optimized version for avoiding memory leaks. returns error 249 | // if index is invalid. 250 | func SliceDeleteAtIndex(sl *[]Ki, idx int) error { 251 | if err := SliceIsValidIndex(sl, idx); err != nil { 252 | return err 253 | } 254 | // this copy makes sure there are no memory leaks 255 | sz := len(*sl) 256 | copy((*sl)[idx:], (*sl)[idx+1:]) 257 | (*sl)[sz-1] = nil 258 | (*sl) = (*sl)[:sz-1] 259 | return nil 260 | } 261 | 262 | // DeleteAtIndex deletes item at index -- does not do any further management 263 | // deleted item -- optimized version for avoiding memory leaks. returns error 264 | // if index is invalid. 265 | func (sl *Slice) DeleteAtIndex(idx int) error { 266 | return SliceDeleteAtIndex((*[]Ki)(sl), idx) 267 | } 268 | 269 | // SliceMove moves element from one position to another. Returns error if 270 | // either index is invalid. 271 | func SliceMove(sl *[]Ki, frm, to int) error { 272 | if err := SliceIsValidIndex(sl, frm); err != nil { 273 | return err 274 | } 275 | if err := SliceIsValidIndex(sl, to); err != nil { 276 | return err 277 | } 278 | if frm == to { 279 | return nil 280 | } 281 | tmp := (*sl)[frm] 282 | SliceDeleteAtIndex(sl, frm) 283 | SliceInsert(sl, tmp, to) 284 | return nil 285 | } 286 | 287 | // Move element from one position to another. Returns error if either index 288 | // is invalid. 289 | func (sl *Slice) Move(frm, to int) error { 290 | return SliceMove((*[]Ki)(sl), frm, to) 291 | } 292 | 293 | // SliceSwap swaps elements between positions. Returns error if either index is invalid 294 | func SliceSwap(sl *[]Ki, i, j int) error { 295 | if err := SliceIsValidIndex(sl, i); err != nil { 296 | return err 297 | } 298 | if err := SliceIsValidIndex(sl, j); err != nil { 299 | return err 300 | } 301 | if i == j { 302 | return nil 303 | } 304 | (*sl)[j], (*sl)[i] = (*sl)[i], (*sl)[j] 305 | return nil 306 | } 307 | 308 | // Swap elements between positions. Returns error if either index is invalid 309 | func (sl *Slice) Swap(i, j int) error { 310 | return SliceSwap((*[]Ki)(sl), i, j) 311 | } 312 | 313 | // TypeAndNames returns a kit.TypeAndNameList of elements in the slice -- 314 | // useful for Ki ConfigChildren. 315 | func (sl *Slice) TypeAndNames() kit.TypeAndNameList { 316 | if len(*sl) == 0 { 317 | return nil 318 | } 319 | tn := make(kit.TypeAndNameList, len(*sl)) 320 | for _, kid := range *sl { 321 | tn.Add(Type(kid), kid.Name()) 322 | } 323 | return tn 324 | } 325 | 326 | /////////////////////////////////////////////////////////////////////////// 327 | // Config 328 | 329 | // Config is a major work-horse routine for minimally-destructive reshaping of 330 | // a tree structure to fit a target configuration, specified in terms of a 331 | // type-and-name list. If the node is != nil, then it has UpdateStart / End 332 | // logic applied to it, only if necessary, as indicated by mods, updt return 333 | // values. 334 | func (sl *Slice) Config(n Ki, config kit.TypeAndNameList) (mods, updt bool) { 335 | mods, updt = false, false 336 | // first make a map for looking up the indexes of the names 337 | nm := make(map[string]int) 338 | for i, tn := range config { 339 | nm[tn.Name] = i 340 | } 341 | // first remove any children not in the config 342 | sz := len(*sl) 343 | for i := sz - 1; i >= 0; i-- { 344 | kid := (*sl)[i] 345 | knm := kid.Name() 346 | ti, ok := nm[knm] 347 | if !ok { 348 | sl.configDeleteKid(kid, i, n, &mods, &updt) 349 | } else if Type(kid) != config[ti].Type { 350 | sl.configDeleteKid(kid, i, n, &mods, &updt) 351 | } 352 | } 353 | // next add and move items as needed -- in order so guaranteed 354 | for i, tn := range config { 355 | kidx, ok := sl.IndexByName(tn.Name, i) 356 | if !ok { 357 | setMods(n, &mods, &updt) 358 | nkid := NewOfType(tn.Type) 359 | nkid.SetName(tn.Name) 360 | InitNode(nkid) 361 | sl.Insert(nkid, i) 362 | if n != nil { 363 | SetParent(nkid, n) 364 | n.SetChildAdded() 365 | } 366 | } else { 367 | if kidx != i { 368 | setMods(n, &mods, &updt) 369 | sl.Move(kidx, i) 370 | } 371 | } 372 | } 373 | DelMgr.DestroyDeleted() 374 | return 375 | } 376 | 377 | func setMods(n Ki, mods *bool, updt *bool) { 378 | if !*mods { 379 | *mods = true 380 | if n != nil { 381 | *updt = n.UpdateStart() 382 | } 383 | } 384 | } 385 | 386 | func (sl *Slice) configDeleteKid(kid Ki, i int, n Ki, mods, updt *bool) { 387 | if !*mods { 388 | *mods = true 389 | if n != nil { 390 | *updt = n.UpdateStart() 391 | n.SetFlag(int(ChildDeleted)) 392 | } 393 | } 394 | kid.SetFlag(int(NodeDeleted)) 395 | kid.NodeSignal().Emit(kid, int64(NodeSignalDeleting), nil) 396 | SetParent(kid, nil) 397 | DelMgr.Add(kid) 398 | sl.DeleteAtIndex(i) 399 | UpdateReset(kid) // it won't get the UpdateEnd from us anymore -- init fresh in any case 400 | } 401 | 402 | // CopyFrom another Slice. It is efficient by using the Config method 403 | // which attempts to preserve any existing nodes in the destination 404 | // if they have the same name and type -- so a copy from a source to 405 | // a target that only differ minimally will be minimally destructive. 406 | // it is essential that child names are unique. 407 | func (sl *Slice) CopyFrom(frm Slice) { 408 | sl.ConfigCopy(nil, frm) 409 | for i, kid := range *sl { 410 | fmk := frm[i] 411 | kid.CopyFrom(fmk) 412 | } 413 | } 414 | 415 | // ConfigCopy uses Config method to copy name / type config of Slice from source 416 | // If n is != nil then Update etc is called properly. 417 | // it is essential that child names are unique. 418 | func (sl *Slice) ConfigCopy(n Ki, frm Slice) { 419 | sz := len(frm) 420 | if sz > 0 || n == nil { 421 | cfg := make(kit.TypeAndNameList, sz) 422 | for i, kid := range frm { 423 | cfg[i].Type = Type(kid) 424 | cfg[i].Name = kid.Name() 425 | } 426 | mods, updt := sl.Config(n, cfg) 427 | if mods && n != nil { 428 | n.UpdateEnd(updt) 429 | } 430 | } else { 431 | n.DeleteChildren(true) 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /ki/version.go: -------------------------------------------------------------------------------- 1 | // WARNING: auto-generated by Makefile release target -- run 'make release' to update 2 | 3 | package ki 4 | 5 | const ( 6 | Version = "v1.1.17" 7 | GitCommit = "018e731" // the commit JUST BEFORE the release 8 | VersionDate = "2023-10-05 21:52" // UTC 9 | ) 10 | -------------------------------------------------------------------------------- /kit/README.md: -------------------------------------------------------------------------------- 1 | # kit 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/goki/ki/kit)](https://goreportcard.com/report/github.com/goki/ki/kit) 4 | [![GoDoc](https://godoc.org/github.com/goki/ki/kit?status.svg)](http://godoc.org/github.com/goki/ki/kit) 5 | 6 | Package `kit` provides various reflect type functions for GoKi system (KiType = KiT = kit -- also a bit of a "kit" collection of low-level system functions), including: 7 | 8 | * `kit.TypeRegistry (types.go)` for associating string names with 9 | `reflect.Type` values, to allow dynamic marshaling of structs, and also 10 | bidirectional string conversion of const int iota (enum) types. It is used 11 | by the GoKi ki system, hence the kit (ki types) name. 12 | 13 | To register a new type, add: 14 | 15 | ```Go 16 | var KiT_TypeName = kit.Types.AddType(&TypeName{}, [props|nil]) 17 | ``` 18 | 19 | where the props is a `map[string]interface{}` of optional properties that can 20 | be associated with the type -- this is used in the GoGi graphical interface 21 | system for example to color objects of different types using the 22 | background-color property. KiT_TypeName variable can be conveniently used 23 | wherever a reflect.Type of that type is needed. 24 | 25 | * `kit.EnumRegistry (enums.go)` that registers constant int iota (aka enum) types, and 26 | provides general conversion utilities to / from string, int64, general 27 | properties associated with enum types, and deals with bit flags 28 | 29 | * `kit.Type (type.go)` struct provides JSON and XML Marshal / Unmarshal functions for 30 | saving / loading reflect.Type using registrered type names. 31 | 32 | * `convert.go`: robust interface{}-based type conversion routines that are 33 | useful in more lax user-interface contexts where "common sense" conversions 34 | between strings, numbers etc are useful 35 | 36 | * `embeds.go`: various functions for managing embedded struct types, e.g., 37 | determining if a given type embeds another type (directly or indirectly), 38 | and iterating over fields to flatten the otherwise nested nature of the 39 | field encoding in embedded types. 40 | -------------------------------------------------------------------------------- /kit/convert_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func AFun(aa any) bool { 13 | return IfaceIsNil(aa) 14 | } 15 | 16 | func TestIfaceIsNil(t *testing.T) { 17 | ai := any(a) 18 | 19 | if IfaceIsNil(ai) != false { 20 | t.Errorf("should be non-nil: %v\n", ai) 21 | } 22 | 23 | var ap *A 24 | api := any(ap) 25 | 26 | if IfaceIsNil(api) != true { 27 | t.Errorf("should be nil: %v\n", api) 28 | } 29 | 30 | if AFun(ap) != true { 31 | t.Errorf("should be nil: %v\n", ap) 32 | } 33 | 34 | if AFun(&a) != false { 35 | t.Errorf("should be non-nil: %v\n", &a) 36 | } 37 | 38 | } 39 | 40 | func TestConverts(t *testing.T) { 41 | fv := 3.14 42 | iv := 10 43 | sv := "25" 44 | // bv := true 45 | 46 | // note: this does not work 47 | // reflect.ValueOf(&fv).Elem().Set(reflect.ValueOf("1.58").Convert(reflect.TypeOf(fv))) 48 | ok := false 49 | 50 | ft := "1.58" 51 | ok = SetRobust(&fv, ft) 52 | fs := fmt.Sprintf("%v", fv) 53 | if !ok || fs != ft { 54 | t.Errorf("Float convert error: %v != %v, ok: %v\n", fs, ft, ok) 55 | } 56 | 57 | it := "1" 58 | ok = SetRobust(&iv, true) 59 | is := fmt.Sprintf("%v", iv) 60 | if !ok || is != it { 61 | t.Errorf("Int convert error: %v != %v, ok: %v\n", is, it, ok) 62 | } 63 | 64 | st := "22" 65 | ok = SetRobust(&sv, 22) 66 | ss := fmt.Sprintf("%v", sv) 67 | if !ok || ss != st { 68 | t.Errorf("String convert error: %v != %v, ok: %v\n", ss, st, ok) 69 | } 70 | tc := C{} 71 | InitC() 72 | ok = SetRobust(&tc, c) 73 | // fmt.Printf("tc %+v\n", tc) 74 | if !ok || tc != c { 75 | t.Errorf("Struct convert error: %+v != %+v, ok: %v\n", c, tc, ok) 76 | } 77 | } 78 | 79 | func TestCopySlice(t *testing.T) { 80 | var tof []float32 81 | var tos []string 82 | fmf := []float32{1, 2, 3} 83 | fms := []string{"3", "4", "5"} 84 | err := CopySliceRobust(&tof, fmf) 85 | if err != nil { 86 | t.Errorf("copy float: %s\n", err.Error()) 87 | } 88 | for i := range fmf { 89 | if tof[i] != fmf[i] { 90 | t.Errorf("tof %d: %g != %g\n", i, tof[i], fmf[i]) 91 | } 92 | } 93 | err = CopySliceRobust(&tos, fms) 94 | if err != nil { 95 | t.Errorf("copy string: %s\n", err.Error()) 96 | } 97 | for i := range fms { 98 | if tos[i] != fms[i] { 99 | t.Errorf("tos %d: %s != %s\n", i, tos[i], fms[i]) 100 | } 101 | } 102 | err = CopySliceRobust(&tof, fms) 103 | if err != nil { 104 | t.Errorf("copy float = string: %s\n", err.Error()) 105 | } 106 | for i := range fms { 107 | if ToString(tof[i]) != fms[i] { 108 | t.Errorf("tof %d: %g != %s\n", i, tof[i], fms[i]) 109 | } 110 | } 111 | fms = fms[:2] 112 | err = CopySliceRobust(&tof, fms) 113 | if err != nil { 114 | t.Errorf("copy float = string: %s\n", err.Error()) 115 | } 116 | if len(tof) != len(fms) { 117 | t.Errorf("copy float = string: size not 2: %d\n", len(tof)) 118 | } 119 | for i := range fms { 120 | if ToString(tof[i]) != fms[i] { 121 | t.Errorf("tof %d: %g != %s\n", i, tof[i], fms[i]) 122 | } 123 | } 124 | fms = append(fms, "7") 125 | err = CopySliceRobust(&tof, fms) 126 | if err != nil { 127 | t.Errorf("copy float = string: %s\n", err.Error()) 128 | } 129 | if len(tof) != len(fms) { 130 | t.Errorf("copy float = string: size not 3: %d\n", len(tof)) 131 | } 132 | for i := range fms { 133 | if ToString(tof[i]) != fms[i] { 134 | t.Errorf("tof %d: %g != %s\n", i, tof[i], fms[i]) 135 | } 136 | } 137 | 138 | var toc [][]float32 139 | fmc := [][]string{[]string{"1", "2"}, []string{"3", "4"}} 140 | err = CopySliceRobust(&toc, fmc) 141 | if err != nil { 142 | t.Errorf("copy [][]float = [][]string: %s\n", err.Error()) 143 | } 144 | for i := range fmc { 145 | fmci := fmc[i] 146 | toci := toc[i] 147 | for j := range fmci { 148 | if ToString(toci[j]) != fmci[j] { 149 | t.Errorf("toci,j %d,%d: %g != %s\n", i, j, toci[j], fmci[j]) 150 | } 151 | } 152 | } 153 | } 154 | 155 | func TestCopyMap(t *testing.T) { 156 | var tof map[string]float32 157 | var tos map[string]string 158 | fmf := map[string]float32{"a": 1, "b": 2, "c": 3} 159 | fms := map[string]string{"a": "3", "b": "4", "c": "5"} 160 | err := CopyMapRobust(&tof, fmf) 161 | if err != nil { 162 | t.Errorf("copy float: %s\n", err.Error()) 163 | } 164 | for i := range fmf { 165 | if tof[i] != fmf[i] { 166 | t.Errorf("tof %s: %g != %g\n", i, tof[i], fmf[i]) 167 | } 168 | } 169 | err = CopyMapRobust(&tos, fms) 170 | if err != nil { 171 | t.Errorf("copy string: %s\n", err.Error()) 172 | } 173 | for i := range fms { 174 | if tos[i] != fms[i] { 175 | t.Errorf("tos %s: %s != %s\n", i, tos[i], fms[i]) 176 | } 177 | } 178 | err = CopyMapRobust(&tof, fms) 179 | if err != nil { 180 | t.Errorf("copy float = string: %s\n", err.Error()) 181 | } 182 | for i := range fms { 183 | if ToString(tof[i]) != fms[i] { 184 | t.Errorf("tof %s: %g != %s\n", i, tof[i], fms[i]) 185 | } 186 | } 187 | delete(fms, "b") 188 | err = CopyMapRobust(&tof, fms) 189 | if err != nil { 190 | t.Errorf("copy float = string: %s\n", err.Error()) 191 | } 192 | if len(tof) != len(fms) { 193 | t.Errorf("copy float = string: size not 2: %d\n", len(tof)) 194 | } 195 | for i := range fms { 196 | if ToString(tof[i]) != fms[i] { 197 | t.Errorf("tof %s: %g != %s\n", i, tof[i], fms[i]) 198 | } 199 | } 200 | fms["e"] = "7" 201 | err = CopyMapRobust(&tof, fms) 202 | if err != nil { 203 | t.Errorf("copy float = string: %s\n", err.Error()) 204 | } 205 | if len(tof) != len(fms) { 206 | t.Errorf("copy float = string: size not 3: %d\n", len(tof)) 207 | } 208 | for i := range fms { 209 | if ToString(tof[i]) != fms[i] { 210 | t.Errorf("tof %s: %g != %s\n", i, tof[i], fms[i]) 211 | } 212 | } 213 | 214 | var toc map[string]map[string]float32 215 | fmc := map[string]map[string]string{"q": {"a": "1", "b": "2"}, "z": {"c": "3", "d": "4"}} 216 | err = CopyMapRobust(&toc, fmc) 217 | if err != nil { 218 | t.Errorf("copy map[string]map[string]float = map[string]map[string]string: %s\n", err.Error()) 219 | } 220 | for i := range fmc { 221 | fmci := fmc[i] 222 | toci := toc[i] 223 | for j := range fmci { 224 | if ToString(toci[j]) != fmci[j] { 225 | t.Errorf("toci,j %s,%s: %g != %s\n", i, j, toci[j], fmci[j]) 226 | } 227 | } 228 | } 229 | } 230 | 231 | func TestSetRobustFomString(t *testing.T) { 232 | ta := A{} 233 | ta.Mbr1 = "av" 234 | ta.Mbr2 = 22 235 | SetRobust(&ta, `{"Mbr1":"aa", "Mbr2":14}`) // note: only uses " 236 | 237 | flts := []float32{1, 2, 3} 238 | SetRobust(&flts, `[3, 4, 5]`) 239 | 240 | mp := map[string]float32{"a": 1, "b": 2, "c": 3} 241 | SetRobust(&mp, `{"d":3,"e":4,"f":5}`) 242 | } 243 | -------------------------------------------------------------------------------- /kit/embeds.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "log" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | // This file contains helpful functions for dealing with embedded structs, in 14 | // the reflect system 15 | 16 | // FlatFieldsTypeFunc calls a function on all the primary fields of a given 17 | // struct type, including those on anonymous embedded structs that this struct 18 | // has, passing the current (embedded) type and StructField -- effectively 19 | // flattens the reflect field list -- if fun returns false then iteration 20 | // stops -- overall rval is false if iteration was stopped or there was an 21 | // error (logged), true otherwise 22 | func FlatFieldsTypeFunc(typ reflect.Type, fun func(typ reflect.Type, field reflect.StructField) bool) bool { 23 | typ = NonPtrType(typ) 24 | if typ.Kind() != reflect.Struct { 25 | log.Printf("kit.FlatFieldsTypeFunc: Must call on a struct type, not: %v\n", typ) 26 | return false 27 | } 28 | rval := true 29 | for i := 0; i < typ.NumField(); i++ { 30 | f := typ.Field(i) 31 | if f.Type.Kind() == reflect.Struct && f.Anonymous { 32 | rval = FlatFieldsTypeFunc(f.Type, fun) // no err here 33 | if !rval { 34 | break 35 | } 36 | } else { 37 | rval = fun(typ, f) 38 | if !rval { 39 | break 40 | } 41 | } 42 | } 43 | return rval 44 | } 45 | 46 | // AllFieldsTypeFunc calls a function on all the fields of a given struct type, 47 | // including those on *any* embedded structs that this struct has -- if fun 48 | // returns false then iteration stops -- overall rval is false if iteration 49 | // was stopped or there was an error (logged), true otherwise. 50 | func AllFieldsTypeFunc(typ reflect.Type, fun func(typ reflect.Type, field reflect.StructField) bool) bool { 51 | typ = NonPtrType(typ) 52 | if typ.Kind() != reflect.Struct { 53 | log.Printf("kit.AllFieldsTypeFunc: Must call on a struct type, not: %v\n", typ) 54 | return false 55 | } 56 | rval := true 57 | for i := 0; i < typ.NumField(); i++ { 58 | f := typ.Field(i) 59 | if f.Type.Kind() == reflect.Struct { 60 | rval = AllFieldsTypeFunc(f.Type, fun) // no err here 61 | if !rval { 62 | break 63 | } 64 | } else { 65 | rval = fun(typ, f) 66 | if !rval { 67 | break 68 | } 69 | } 70 | } 71 | return rval 72 | } 73 | 74 | // FlatFieldsValueFunc calls a function on all the primary fields of a 75 | // given struct value (must pass a pointer to the struct) including those on 76 | // anonymous embedded structs that this struct has, passing the current 77 | // (embedded) type and StructField -- effectively flattens the reflect field 78 | // list 79 | func FlatFieldsValueFunc(stru any, fun func(stru any, typ reflect.Type, field reflect.StructField, fieldVal reflect.Value) bool) bool { 80 | vv := reflect.ValueOf(stru) 81 | if stru == nil || vv.Kind() != reflect.Ptr { 82 | log.Printf("kit.FlatFieldsValueFunc: must pass a non-nil pointer to the struct: %v\n", stru) 83 | return false 84 | } 85 | v := NonPtrValue(vv) 86 | if !v.IsValid() { 87 | return true 88 | } 89 | typ := v.Type() 90 | if typ.Kind() != reflect.Struct { 91 | // log.Printf("kit.FlatFieldsValueFunc: non-pointer type is not a struct: %v\n", typ.String()) 92 | return false 93 | } 94 | rval := true 95 | for i := 0; i < typ.NumField(); i++ { 96 | f := typ.Field(i) 97 | vf := v.Field(i) 98 | if !vf.CanInterface() { 99 | continue 100 | } 101 | vfi := vf.Interface() 102 | if vfi == stru { 103 | continue 104 | } 105 | if f.Type.Kind() == reflect.Struct && f.Anonymous { 106 | // key to take addr here so next level is addressable 107 | rval = FlatFieldsValueFunc(PtrValue(vf).Interface(), fun) 108 | if !rval { 109 | break 110 | } 111 | } else { 112 | rval = fun(vfi, typ, f, vf) 113 | if !rval { 114 | break 115 | } 116 | } 117 | } 118 | return rval 119 | } 120 | 121 | // FlatFields returns a slice list of all the StructField type information for 122 | // fields of given type and any embedded types -- returns nil on error 123 | // (logged) 124 | func FlatFields(typ reflect.Type) []reflect.StructField { 125 | ff := make([]reflect.StructField, 0) 126 | falseErr := FlatFieldsTypeFunc(typ, func(typ reflect.Type, field reflect.StructField) bool { 127 | ff = append(ff, field) 128 | return true 129 | }) 130 | if falseErr == false { 131 | return nil 132 | } 133 | return ff 134 | } 135 | 136 | // AllFields returns a slice list of all the StructField type information for 137 | // all elemental fields of given type and all embedded types -- returns nil on 138 | // error (logged) 139 | func AllFields(typ reflect.Type) []reflect.StructField { 140 | ff := make([]reflect.StructField, 0) 141 | falseErr := AllFieldsTypeFunc(typ, func(typ reflect.Type, field reflect.StructField) bool { 142 | ff = append(ff, field) 143 | return true 144 | }) 145 | if falseErr == false { 146 | return nil 147 | } 148 | return ff 149 | } 150 | 151 | // AllFieldsN returns number of elemental fields in given type 152 | func AllFieldsN(typ reflect.Type) int { 153 | n := 0 154 | falseErr := AllFieldsTypeFunc(typ, func(typ reflect.Type, field reflect.StructField) bool { 155 | n++ 156 | return true 157 | }) 158 | if falseErr == false { 159 | return 0 160 | } 161 | return n 162 | } 163 | 164 | // FlatFieldsVals returns a slice list of all the field reflect.Value's for 165 | // fields of given struct (must pass a pointer to the struct) and any of its 166 | // embedded structs -- returns nil on error (logged) 167 | func FlatFieldVals(stru any) []reflect.Value { 168 | ff := make([]reflect.Value, 0) 169 | falseErr := FlatFieldsValueFunc(stru, func(stru any, typ reflect.Type, field reflect.StructField, fieldVal reflect.Value) bool { 170 | ff = append(ff, fieldVal) 171 | return true 172 | }) 173 | if falseErr == false { 174 | return nil 175 | } 176 | return ff 177 | } 178 | 179 | // FlatFieldInterfaces returns a slice list of all the field interface{} 180 | // values *as pointers to the field value* (i.e., calling Addr() on the Field 181 | // Value) for fields of given struct (must pass a pointer to the struct) and 182 | // any of its embedded structs -- returns nil on error (logged) 183 | func FlatFieldInterfaces(stru any) []any { 184 | ff := make([]any, 0) 185 | falseErr := FlatFieldsValueFunc(stru, func(stru any, typ reflect.Type, field reflect.StructField, fieldVal reflect.Value) bool { 186 | ff = append(ff, PtrValue(fieldVal).Interface()) 187 | return true 188 | }) 189 | if falseErr == false { 190 | return nil 191 | } 192 | return ff 193 | } 194 | 195 | // FlatFieldByName returns field in type or embedded structs within type, by 196 | // name -- native function already does flat version, so this is just for 197 | // reference and consistency 198 | func FlatFieldByName(typ reflect.Type, nm string) (reflect.StructField, bool) { 199 | return typ.FieldByName(nm) 200 | } 201 | 202 | // FieldByPath returns field in type or embedded structs within type, by a 203 | // dot-separated path -- finds field by name for each level of the path, and 204 | // recurses. 205 | func FieldByPath(typ reflect.Type, path string) (reflect.StructField, bool) { 206 | pels := strings.Split(path, ".") 207 | ctyp := typ 208 | plen := len(pels) 209 | for i, pe := range pels { 210 | fld, ok := ctyp.FieldByName(pe) 211 | if !ok { 212 | log.Printf("kit.FieldByPath: field: %v not found in type: %v, starting from path: %v, in type: %v\n", pe, ctyp.String(), path, typ.String()) 213 | return fld, false 214 | } 215 | if i == plen-1 { 216 | return fld, true 217 | } 218 | ctyp = fld.Type 219 | } 220 | return reflect.StructField{}, false 221 | } 222 | 223 | // FieldValueByPath returns field interface in type or embedded structs within 224 | // type, by a dot-separated path -- finds field by name for each level of the 225 | // path, and recurses. 226 | func FieldValueByPath(stru any, path string) (reflect.Value, bool) { 227 | pels := strings.Split(path, ".") 228 | sval := reflect.ValueOf(stru) 229 | cval := sval 230 | typ := sval.Type() 231 | ctyp := typ 232 | plen := len(pels) 233 | for i, pe := range pels { 234 | _, ok := ctyp.FieldByName(pe) 235 | if !ok { 236 | log.Printf("kit.FieldValueByPath: field: %v not found in type: %v, starting from path: %v, in type: %v\n", pe, cval.Type().String(), path, typ.String()) 237 | return cval, false 238 | } 239 | fval := cval.FieldByName(pe) 240 | if i == plen-1 { 241 | return fval, true 242 | } 243 | cval = fval 244 | ctyp = fval.Type() 245 | } 246 | return reflect.Value{}, false 247 | } 248 | 249 | // FlatFieldTag returns given tag value in field in type or embedded structs 250 | // within type, by name -- empty string if not set or field not found 251 | func FlatFieldTag(typ reflect.Type, nm, tag string) string { 252 | fld, ok := typ.FieldByName(nm) 253 | if !ok { 254 | return "" 255 | } 256 | return fld.Tag.Get(tag) 257 | } 258 | 259 | // FlatFieldValueByName finds field in object and embedded objects, by name, 260 | // returning reflect.Value of field -- native version of Value function 261 | // already does flat find, so this just provides a convenient wrapper 262 | func FlatFieldValueByName(stru any, nm string) reflect.Value { 263 | vv := reflect.ValueOf(stru) 264 | if stru == nil || vv.Kind() != reflect.Ptr { 265 | log.Printf("kit.FlatFieldsValueFunc: must pass a non-nil pointer to the struct: %v\n", stru) 266 | return reflect.Value{} 267 | } 268 | v := NonPtrValue(vv) 269 | return v.FieldByName(nm) 270 | } 271 | 272 | // FlatFieldInterfaceByName finds field in object and embedded objects, by 273 | // name, returning interface{} to pointer of field, or nil if not found 274 | func FlatFieldInterfaceByName(stru any, nm string) any { 275 | ff := FlatFieldValueByName(stru, nm) 276 | if !ff.IsValid() { 277 | return nil 278 | } 279 | return PtrValue(ff).Interface() 280 | } 281 | 282 | // TypeEmbeds checks if given type embeds another type, at any level of 283 | // recursive embedding (including being the type itself) 284 | func TypeEmbeds(typ, embed reflect.Type) bool { 285 | typ = NonPtrType(typ) 286 | embed = NonPtrType(embed) 287 | if typ == embed { 288 | return true 289 | } 290 | for i := 0; i < typ.NumField(); i++ { 291 | f := typ.Field(i) 292 | if f.Type.Kind() == reflect.Struct && f.Anonymous { 293 | // fmt.Printf("typ %v anon struct %v\n", typ.Name(), f.Name) 294 | if f.Type == embed { 295 | return true 296 | } 297 | return TypeEmbeds(f.Type, embed) 298 | } 299 | } 300 | return false 301 | } 302 | 303 | // Embed returns the embedded struct of given type within given struct 304 | func Embed(stru any, embed reflect.Type) any { 305 | if IfaceIsNil(stru) { 306 | return nil 307 | } 308 | v := NonPtrValue(reflect.ValueOf(stru)) 309 | typ := v.Type() 310 | if typ == embed { 311 | return PtrValue(v).Interface() 312 | } 313 | for i := 0; i < typ.NumField(); i++ { 314 | f := typ.Field(i) 315 | if f.Type.Kind() == reflect.Struct && f.Anonymous { // anon only avail on StructField fm typ 316 | vf := v.Field(i) 317 | vfpi := PtrValue(vf).Interface() 318 | if f.Type == embed { 319 | return vfpi 320 | } 321 | rv := Embed(vfpi, embed) 322 | if rv != nil { 323 | return rv 324 | } 325 | } 326 | } 327 | return nil 328 | } 329 | 330 | // EmbedImplements checks if given type implements given interface, or 331 | // it embeds a type that does so -- must pass a type constructed like this: 332 | // reflect.TypeOf((*gi.Node2D)(nil)).Elem() or just reflect.TypeOf(ki.BaseIface()) 333 | func EmbedImplements(typ, iface reflect.Type) bool { 334 | if iface.Kind() != reflect.Interface { 335 | log.Printf("kit.TypeRegistry EmbedImplements -- type is not an interface: %v\n", iface) 336 | return false 337 | } 338 | if typ.Implements(iface) { 339 | return true 340 | } 341 | if reflect.PtrTo(typ).Implements(iface) { // typically need the pointer type to impl 342 | return true 343 | } 344 | typ = NonPtrType(typ) 345 | if typ.Implements(iface) { // try it all possible ways.. 346 | return true 347 | } 348 | if typ.Kind() != reflect.Struct { 349 | return false 350 | } 351 | for i := 0; i < typ.NumField(); i++ { 352 | f := typ.Field(i) 353 | if f.Type.Kind() == reflect.Struct && f.Anonymous { 354 | rv := EmbedImplements(f.Type, iface) 355 | if rv { 356 | return true 357 | } 358 | } 359 | } 360 | return false 361 | } 362 | -------------------------------------------------------------------------------- /kit/embeds_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | type A struct { 14 | Mbr1 string 15 | Mbr2 int 16 | } 17 | 18 | type AIf interface { 19 | AFun() bool 20 | } 21 | 22 | func (a *A) AFun() bool { 23 | return true 24 | } 25 | 26 | var _ AIf = &A{} 27 | 28 | type B struct { 29 | A 30 | Mbr3 string 31 | Mbr4 int 32 | } 33 | 34 | type C struct { 35 | B 36 | Mbr5 string 37 | Mbr6 int 38 | } 39 | 40 | type D struct { 41 | Mbr5 string 42 | Mbr6 int 43 | NmdA A 44 | } 45 | 46 | var a = A{} 47 | var b = B{} 48 | var c = C{} 49 | var d = D{} 50 | 51 | func InitC() { 52 | c.Mbr1 = "mbr1 string" 53 | c.Mbr2 = 2 54 | c.Mbr3 = "mbr3 string" 55 | c.Mbr4 = 4 56 | c.Mbr5 = "mbr5 string" 57 | c.Mbr6 = 6 58 | } 59 | 60 | func InitD() { 61 | d.Mbr5 = "mbr5 string" 62 | d.Mbr6 = 6 63 | d.NmdA.Mbr1 = "a in d" 64 | d.NmdA.Mbr2 = 2 65 | } 66 | 67 | func TestTypeEmbeds(t *testing.T) { 68 | InitC() 69 | 70 | a_in_a := TypeEmbeds(reflect.TypeOf(a), reflect.TypeOf(a)) 71 | // fmt.Printf("A embeds A: %v\n", a_in_a) 72 | 73 | b_in_a := TypeEmbeds(reflect.TypeOf(a), reflect.TypeOf(b)) 74 | // fmt.Printf("A embeds B: %v\n", b_in_a) 75 | 76 | a_in_b := TypeEmbeds(reflect.TypeOf(b), reflect.TypeOf(a)) 77 | // fmt.Printf("B embeds A: %v\n", a_in_b) 78 | 79 | a_in_c := TypeEmbeds(reflect.TypeOf(c), reflect.TypeOf(a)) 80 | // fmt.Printf("C embeds A: %v\n", a_in_c) 81 | 82 | aiftype := reflect.TypeOf((*AIf)(nil)).Elem() 83 | 84 | // note: MUST use pointer for checking implements for pointer receivers! 85 | // fmt.Printf("a implements Aif %v\n", reflect.TypeOf(&a).Implements(aiftype)) 86 | 87 | aif_in_c := EmbedImplements(reflect.TypeOf(c), aiftype) 88 | // fmt.Printf("C implements AIf: %v\n", aif_in_c) 89 | 90 | aif_in_d := EmbedImplements(reflect.TypeOf(d), aiftype) 91 | // fmt.Printf("D implements AIf: %v\n", aif_in_d) 92 | 93 | if a_in_a != true || b_in_a != false || a_in_b != true || a_in_c != true || aif_in_c != true || aif_in_d != false { 94 | t.Errorf("something wrong in TypeEmbeds: should have: true, false, true, true, true, false is: %v %v %v %v %v %v\n", a_in_a, b_in_a, a_in_b, a_in_c, aif_in_c, aif_in_d) 95 | } 96 | } 97 | 98 | func TestEmbed(t *testing.T) { 99 | InitC() 100 | 101 | aa := Embed(&a, reflect.TypeOf(a)) 102 | aas := fmt.Sprintf("%+v", aa) 103 | aat := "&{Mbr1: Mbr2:0}" 104 | if aas != aat { 105 | t.Errorf("Didn't get proper embedded members of A from A: %v != %v\n", aas, aat) 106 | } 107 | 108 | ca := Embed(&c, reflect.TypeOf(a)) 109 | cas := fmt.Sprintf("%+v", ca) 110 | cat := "&{Mbr1:mbr1 string Mbr2:2}" 111 | if cas != cat { 112 | t.Errorf("Didn't get proper embedded members of C from A: %v != %v\n", cas, cat) 113 | } 114 | } 115 | 116 | func TestFlatFields(t *testing.T) { 117 | InitC() 118 | 119 | // FlatFieldsTypeFunc(reflect.TypeOf(c), func(typ reflect.Type, field reflect.StructField) { 120 | // fmt.Printf("typ: %v, field: %v\n", typ, field) 121 | // }) 122 | 123 | // FlatFieldsValueFunc(c, func(stru interface{}, typ reflect.Type, field reflect.StructField, fieldVal reflect.Value) { 124 | // fmt.Printf("typ: %v, field: %v val: %v\n", typ, field, fieldVal) 125 | // }) 126 | 127 | // note: these test the above TypeFun and ValueFun 128 | 129 | ff := FlatFields(reflect.TypeOf(c)) 130 | ffs := fmt.Sprintf("%v", ff) 131 | fft := `[{Mbr1 string 0 [0] false} {Mbr2 int 16 [1] false} {Mbr3 string 24 [1] false} {Mbr4 int 40 [2] false} {Mbr5 string 48 [1] false} {Mbr6 int 64 [2] false}]` 132 | if ffs != fft { 133 | t.Errorf("Didn't get proper flat field list of C: %v != %v\n", ffs, fft) 134 | } 135 | 136 | ffv := FlatFieldVals(&c) 137 | ffvs := fmt.Sprintf("%v", ffv) 138 | ffvt := `[mbr1 string mbr3 string mbr5 string ]` 139 | if ffvs != ffvt { 140 | t.Errorf("Didn't get proper flat field value list of C: %v != %v\n", ffvs, ffvt) 141 | } 142 | 143 | ffi := FlatFieldInterfaces(&c) 144 | ffis := "" 145 | for _, fi := range ffi { 146 | ffis += fmt.Sprintf("%v,", NonPtrInterface(fi)) 147 | } 148 | ffit := `mbr1 string,2,mbr3 string,4,mbr5 string,6,` 149 | if ffis != ffit { 150 | t.Errorf("Didn't get proper flat field interface list of C: %v != %v\n", ffis, ffit) 151 | } 152 | } 153 | 154 | func TestFlatFieldsByName(t *testing.T) { 155 | InitC() 156 | 157 | fif, _ := FlatFieldByName(reflect.TypeOf(c), "Mbr3") 158 | fifs := fmt.Sprintf("%v", fif) 159 | fift := `{Mbr3 string 24 [0 1] false}` 160 | if fifs != fift { 161 | t.Errorf("Didn't get proper find flat field by name: %v != %v\n", fifs, fift) 162 | } 163 | 164 | fifn, _ := FlatFieldByName(reflect.TypeOf(c), "Mbr31") 165 | fifns := fmt.Sprintf("%v", fifn) 166 | fifnt := `{ 0 [] false}` 167 | if fifns != fifnt { 168 | t.Errorf("Didn't get proper nil find flat field by name: %v != %v\n", fifns, fifnt) 169 | } 170 | 171 | fifv := FlatFieldValueByName(&c, "Mbr4") 172 | fifvs := fmt.Sprintf("%v", fifv) 173 | fifvt := `4` 174 | if fifvs != fifvt { 175 | t.Errorf("Didn't get proper find flat field value by name: %v != %v\n", fifvs, fifvt) 176 | } 177 | 178 | fifi := FlatFieldInterfaceByName(&c, "Mbr2") 179 | fifis := fmt.Sprintf("%v", NonPtrInterface(fifi)) 180 | fifit := `2` 181 | if fifis != fifit { 182 | t.Errorf("Didn't get proper find flat field value by name: %v != %v\n", fifis, fifit) 183 | } 184 | 185 | } 186 | 187 | func TestFieldPaths(t *testing.T) { 188 | InitD() 189 | 190 | fld, ok := FieldByPath(reflect.TypeOf(d), "NmdA.Mbr1") 191 | if !ok { 192 | t.Errorf("FieldByPath failed per err msg, fld %v\n", fld.Name) 193 | } 194 | 195 | fi, ok := FieldValueByPath(d, "NmdA.Mbr1") 196 | if !ok { 197 | t.Errorf("FieldValueByPath failed per err msg, fi %v\n", fi) 198 | } 199 | // fmt.Printf("fi: %v\n", fi) 200 | } 201 | -------------------------------------------------------------------------------- /kit/enums_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "encoding/json" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/goki/ki/bitflag" 13 | ) 14 | 15 | func TestEnums(t *testing.T) { 16 | 17 | et := TestFlag1 18 | 19 | i := EnumIfaceToInt64(et) 20 | if i != int64(et) { 21 | t.Errorf("EnumIfaceToInt64 failed %v != %v", i, int64(et)) 22 | } 23 | 24 | err := SetEnumIfaceFromInt64(&et, 2, KiT_TestFlags) 25 | if err != nil { 26 | t.Errorf("%v", err) 27 | } 28 | if et != TestFlag2 { 29 | t.Errorf("SetEnumIfaceFromInt64 failed %v != %v", et, TestFlag2) 30 | } 31 | 32 | ei := EnumIfaceFromInt64(1, KiT_TestFlags) 33 | if ei == nil { 34 | t.Errorf("EnumIfaceFromInt64 nil: %v", ei) 35 | } 36 | eiv, ok := ToInt(ei) 37 | if !ok { 38 | t.Errorf("EnumIfaceFromInt64 ToInt failed\n") 39 | } 40 | if eiv != int64(TestFlag1) { 41 | t.Errorf("EnumIfaceFromInt64 failed %v != %v", eiv, TestFlag1) 42 | } 43 | eit := ei.(TestFlags) 44 | if eit != TestFlag1 { 45 | t.Errorf("EnumIfaceFromInt64 failed %v != %v", eit, TestFlag1) 46 | } 47 | 48 | es := EnumInt64ToString(2, KiT_TestFlags) 49 | if es != "TestFlag2" { 50 | t.Errorf("EnumInt64ToString failed %v != %v", es, TestFlag1) 51 | } 52 | 53 | et = TestFlag2 54 | es = EnumIfaceToString(et) 55 | if es != "TestFlag2" { 56 | t.Errorf("EnumIfaceToString failed %v != %v", es, TestFlag2) 57 | } 58 | 59 | es = Enums.EnumIfaceToAltString(et) 60 | if es != "flag2" { 61 | t.Errorf("EnumToAltString failed %v != %v", es, "flag2") 62 | } 63 | 64 | err = SetEnumValueFromString(reflect.ValueOf(&et), "TestFlag1") 65 | if err != nil { 66 | t.Errorf("%v", err) 67 | } 68 | if et != TestFlag1 { 69 | t.Errorf("SetEnumValueFromString failed %v != %v", et, TestFlag1) 70 | } 71 | 72 | err = Enums.SetEnumValueFromAltString(reflect.ValueOf(&et), "flag2") 73 | if err != nil { 74 | t.Errorf("%v", err) 75 | } 76 | if et != TestFlag2 { 77 | t.Errorf("SetEnumValueFromAltString failed %v != %v", et, TestFlag2) 78 | } 79 | 80 | err = Enums.SetEnumValueFromStringAltFirst(reflect.ValueOf(&et), "flag1") 81 | if err != nil { 82 | t.Errorf("%v", err) 83 | } 84 | if et != TestFlag1 { 85 | t.Errorf("SetEnumValueFromStringAltFirst failed %v != %v", et, TestFlag1) 86 | } 87 | 88 | err = Enums.SetEnumValueFromStringAltFirst(reflect.ValueOf(&et), "TestFlag2") 89 | if err != nil { 90 | t.Errorf("%v", err) 91 | } 92 | if et != TestFlag2 { 93 | t.Errorf("SetEnumValueFromStringAltFirst failed %v != %v", et, TestFlag2) 94 | } 95 | 96 | err = SetEnumValueFromInt64(reflect.ValueOf(&et), 1) 97 | if err != nil { 98 | t.Errorf("%v", err) 99 | } 100 | if et != TestFlag1 { 101 | t.Errorf("SetEnumValueFromInt64 failed %v != %v", et, TestFlag1) 102 | } 103 | 104 | bf := int64(0) 105 | bitflag.Set(&bf, int(TestFlag1), int(TestFlag2)) 106 | es = BitFlagsToString(bf, TestFlagsN) 107 | if es != "TestFlag1|TestFlag2" { 108 | t.Errorf("BitFlagsToString failed %v != %v", es, "TestFlag1|TestFlag2") 109 | } 110 | 111 | bf2 := int64(0) 112 | err = BitFlagsFromString(&bf2, es, TestFlagsN) 113 | if err != nil { 114 | t.Errorf("%v", err) 115 | } 116 | es2 := BitFlagsToString(bf2, TestFlagsN) 117 | if es2 != "TestFlag1|TestFlag2" { 118 | t.Errorf("BitFlagsFromString failed %v != %v", es, "TestFlag1|TestFlag2") 119 | } 120 | 121 | } 122 | 123 | func TestEnumJSON(t *testing.T) { 124 | 125 | et := TestFlag1 126 | 127 | b, err := json.Marshal(et) 128 | 129 | if err != nil { 130 | t.Errorf("%v", err) 131 | } 132 | 133 | // fmt.Println(string(b)) 134 | 135 | et = TestFlag2 136 | err = json.Unmarshal(b, &et) 137 | 138 | if err != nil { 139 | t.Errorf("%v", err) 140 | } 141 | 142 | if et != TestFlag1 { 143 | t.Errorf("EnumJSON error, saved as: %v after loading value should be TestFlag1, is: %v\n", string(b), et) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /kit/errs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "errors" 9 | "strings" 10 | 11 | "github.com/goki/ki/ints" 12 | ) 13 | 14 | // AllErrors returns an err as a concatenation of errors (nil if none). 15 | // no more than maxN are included (typically 10). 16 | func AllErrors(errs []error, maxN int) error { 17 | if len(errs) == 0 { 18 | return nil 19 | } 20 | mx := ints.MinInt(maxN, len(errs)) 21 | ers := make([]string, mx) 22 | for i := 0; i < mx; i++ { 23 | ers[i] = errs[i].Error() 24 | } 25 | return errors.New(strings.Join(ers, "\n")) 26 | } 27 | -------------------------------------------------------------------------------- /kit/maps.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "reflect" 11 | "sort" 12 | "strings" 13 | "time" 14 | 15 | "github.com/goki/ki/floats" 16 | "github.com/goki/ki/ints" 17 | ) 18 | 19 | // This file contains helpful functions for dealing with maps, in the reflect 20 | // system 21 | 22 | // MakeMap makes a map that is actually addressable, getting around the hidden 23 | // interface{} that reflect.MakeMap makes, by calling UnhideIfaceValue (from ptrs.go) 24 | func MakeMap(typ reflect.Type) reflect.Value { 25 | return UnhideIfaceValue(reflect.MakeMap(typ)) 26 | } 27 | 28 | // MapValueType returns the type of the value for the given map (which can be 29 | // a pointer to a map or a direct map) -- just Elem() of map type, but using 30 | // this function makes it more explicit what is going on. 31 | func MapValueType(mp any) reflect.Type { 32 | return NonPtrType(reflect.TypeOf(mp)).Elem() 33 | } 34 | 35 | // MapKeyType returns the type of the key for the given map (which can be a 36 | // pointer to a map or a direct map) -- just Key() of map type, but using 37 | // this function makes it more explicit what is going on. 38 | func MapKeyType(mp any) reflect.Type { 39 | return NonPtrType(reflect.TypeOf(mp)).Key() 40 | } 41 | 42 | // MapElsValueFun calls a function on all the "basic" elements of given map -- 43 | // iterates over maps within maps (but not structs, slices within maps). 44 | func MapElsValueFun(mp any, fun func(mp any, typ reflect.Type, key, val reflect.Value) bool) bool { 45 | vv := reflect.ValueOf(mp) 46 | if mp == nil { 47 | log.Printf("kit.MapElsValueFun: must pass a non-nil pointer to the map: %v\n", mp) 48 | return false 49 | } 50 | v := NonPtrValue(vv) 51 | if !v.IsValid() { 52 | return true 53 | } 54 | typ := v.Type() 55 | if typ.Kind() != reflect.Map { 56 | log.Printf("kit.MapElsValueFun: non-pointer type is not a map: %v\n", typ.String()) 57 | return false 58 | } 59 | rval := true 60 | keys := v.MapKeys() 61 | for _, key := range keys { 62 | val := v.MapIndex(key) 63 | vali := val.Interface() 64 | // vt := val.Type() 65 | vt := reflect.TypeOf(vali) 66 | // fmt.Printf("key %v val %v kind: %v\n", key, val, vt.Kind()) 67 | if vt.Kind() == reflect.Map { 68 | rval = MapElsValueFun(vali, fun) 69 | if !rval { 70 | break 71 | } 72 | // } else if vt.Kind() == reflect.Struct { // todo 73 | // rval = MapElsValueFun(vali, fun) 74 | // if !rval { 75 | // break 76 | // } 77 | } else { 78 | rval = fun(vali, typ, key, val) 79 | if !rval { 80 | break 81 | } 82 | } 83 | } 84 | return rval 85 | } 86 | 87 | // MapElsN returns number of elemental fields in given map type 88 | func MapElsN(mp any) int { 89 | n := 0 90 | falseErr := MapElsValueFun(mp, func(mp any, typ reflect.Type, key, val reflect.Value) bool { 91 | n++ 92 | return true 93 | }) 94 | if falseErr == false { 95 | return 0 96 | } 97 | return n 98 | } 99 | 100 | // MapStructElsValueFun calls a function on all the "basic" elements of given 101 | // map or struct -- iterates over maps within maps and fields within structs 102 | func MapStructElsValueFun(mp any, fun func(mp any, typ reflect.Type, val reflect.Value) bool) bool { 103 | vv := reflect.ValueOf(mp) 104 | if mp == nil { 105 | log.Printf("kit.MapElsValueFun: must pass a non-nil pointer to the map: %v\n", mp) 106 | return false 107 | } 108 | v := NonPtrValue(vv) 109 | if !v.IsValid() { 110 | return true 111 | } 112 | typ := v.Type() 113 | vk := typ.Kind() 114 | rval := true 115 | switch vk { 116 | case reflect.Map: 117 | keys := v.MapKeys() 118 | for _, key := range keys { 119 | val := v.MapIndex(key) 120 | vali := val.Interface() 121 | if IfaceIsNil(vali) { 122 | continue 123 | } 124 | vt := reflect.TypeOf(vali) 125 | if vt == nil { 126 | continue 127 | } 128 | vtk := vt.Kind() 129 | switch vtk { 130 | case reflect.Map: 131 | rval = MapStructElsValueFun(vali, fun) 132 | if !rval { 133 | break 134 | } 135 | case reflect.Struct: 136 | rval = MapStructElsValueFun(vali, fun) 137 | if !rval { 138 | break 139 | } 140 | default: 141 | rval = fun(vali, typ, val) 142 | if !rval { 143 | break 144 | } 145 | } 146 | } 147 | case reflect.Struct: 148 | for i := 0; i < typ.NumField(); i++ { 149 | f := typ.Field(i) 150 | vf := v.Field(i) 151 | if !vf.CanInterface() { 152 | continue 153 | } 154 | vfi := vf.Interface() 155 | if vfi == mp { 156 | continue 157 | } 158 | vtk := f.Type.Kind() 159 | switch vtk { 160 | case reflect.Map: 161 | rval = MapStructElsValueFun(vfi, fun) 162 | if !rval { 163 | break 164 | } 165 | case reflect.Struct: 166 | rval = MapStructElsValueFun(vfi, fun) 167 | if !rval { 168 | break 169 | } 170 | default: 171 | rval = fun(vfi, typ, vf) 172 | if !rval { 173 | break 174 | } 175 | } 176 | } 177 | default: 178 | log.Printf("kit.MapStructElsValueFun: non-pointer type is not a map or struct: %v\n", typ.String()) 179 | return false 180 | } 181 | return rval 182 | } 183 | 184 | // MapStructElsN returns number of elemental fields in given map / struct types 185 | func MapStructElsN(mp any) int { 186 | n := 0 187 | falseErr := MapStructElsValueFun(mp, func(mp any, typ reflect.Type, val reflect.Value) bool { 188 | n++ 189 | return true 190 | }) 191 | if falseErr == false { 192 | return 0 193 | } 194 | return n 195 | } 196 | 197 | // MapAdd adds a new blank entry to the map 198 | func MapAdd(mv any) { 199 | mpv := reflect.ValueOf(mv) 200 | mpvnp := NonPtrValue(mpv) 201 | mvtyp := mpvnp.Type() 202 | valtyp := MapValueType(mv) 203 | if valtyp.Kind() == reflect.Interface && valtyp.String() == "interface {}" { 204 | str := "" 205 | valtyp = reflect.TypeOf(str) 206 | } 207 | nkey := reflect.New(MapKeyType(mv)) 208 | nval := reflect.New(valtyp) 209 | if mpvnp.IsNil() { // make a new map 210 | nmp := MakeMap(mvtyp) 211 | mpv.Elem().Set(nmp.Elem()) 212 | mpvnp = NonPtrValue(mpv) 213 | } 214 | mpvnp.SetMapIndex(nkey.Elem(), nval.Elem()) 215 | } 216 | 217 | // MapDelete deletes a key-value from the map (set key to a zero value) 218 | func MapDelete(mv any, key any) { 219 | mpv := reflect.ValueOf(mv) 220 | mpvnp := NonPtrValue(mpv) 221 | mpvnp.SetMapIndex(reflect.ValueOf(key), reflect.Value{}) // delete 222 | } 223 | 224 | // MapDeleteValue deletes a key-value from the map (set key to a zero value) 225 | // -- key is already a reflect.Value 226 | func MapDeleteValue(mv any, key reflect.Value) { 227 | mpv := reflect.ValueOf(mv) 228 | mpvnp := NonPtrValue(mpv) 229 | mpvnp.SetMapIndex(key, reflect.Value{}) // delete 230 | } 231 | 232 | // MapDeleteAll deletes everything from map 233 | func MapDeleteAll(mv any) { 234 | mpv := reflect.ValueOf(mv) 235 | mpvnp := NonPtrValue(mpv) 236 | if mpvnp.Len() == 0 { 237 | return 238 | } 239 | itr := mpvnp.MapRange() 240 | for itr.Next() { 241 | mpvnp.SetMapIndex(itr.Key(), reflect.Value{}) // delete 242 | } 243 | } 244 | 245 | // MapSort sorts keys of map either by key or by value, returns those keys as 246 | // a slice of reflect.Value, as returned by reflect.Value.MapKeys() method 247 | func MapSort(mp any, byKey, ascending bool) []reflect.Value { 248 | mpv := reflect.ValueOf(mp) 249 | mpvnp := NonPtrValue(mpv) 250 | keys := mpvnp.MapKeys() // note: this is a slice of reflect.Value! 251 | if byKey { 252 | ValueSliceSort(keys, ascending) 253 | } else { 254 | MapValueSort(mpvnp, keys, ascending) 255 | } 256 | return keys 257 | } 258 | 259 | // MapValueSort sorts keys of map by values 260 | func MapValueSort(mpvnp reflect.Value, keys []reflect.Value, ascending bool) error { 261 | if len(keys) == 0 { 262 | return nil 263 | } 264 | keyval := keys[0] 265 | felval := mpvnp.MapIndex(keyval) 266 | eltyp := felval.Type() 267 | elnptyp := NonPtrType(eltyp) 268 | vk := elnptyp.Kind() 269 | elval := OnePtrValue(felval) 270 | elif := elval.Interface() 271 | 272 | switch elif.(type) { 273 | case floats.Floater: 274 | sort.Slice(keys, func(i, j int) bool { 275 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Interface().(floats.Floater).Float() 276 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Interface().(floats.Floater).Float() 277 | if ascending { 278 | return iv < jv 279 | } 280 | return iv > jv 281 | }) 282 | return nil 283 | case ints.Inter: 284 | sort.Slice(keys, func(i, j int) bool { 285 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Interface().(ints.Inter).Int() 286 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Interface().(ints.Inter).Int() 287 | if ascending { 288 | return iv < jv 289 | } 290 | return iv > jv 291 | }) 292 | return nil 293 | } 294 | 295 | // try all the numeric types first! 296 | 297 | switch { 298 | case vk >= reflect.Int && vk <= reflect.Int64: 299 | sort.Slice(keys, func(i, j int) bool { 300 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Int() 301 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Int() 302 | if ascending { 303 | return iv < jv 304 | } 305 | return iv > jv 306 | }) 307 | return nil 308 | case vk >= reflect.Uint && vk <= reflect.Uint64: 309 | sort.Slice(keys, func(i, j int) bool { 310 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Uint() 311 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Uint() 312 | if ascending { 313 | return iv < jv 314 | } 315 | return iv > jv 316 | }) 317 | return nil 318 | case vk >= reflect.Float32 && vk <= reflect.Float64: 319 | sort.Slice(keys, func(i, j int) bool { 320 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Float() 321 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Float() 322 | if ascending { 323 | return iv < jv 324 | } 325 | return iv > jv 326 | }) 327 | return nil 328 | case vk == reflect.Struct && ShortTypeName(elnptyp) == "time.Time": 329 | sort.Slice(keys, func(i, j int) bool { 330 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Interface().(time.Time) 331 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Interface().(time.Time) 332 | if ascending { 333 | return iv.Before(jv) 334 | } 335 | return jv.Before(iv) 336 | }) 337 | } 338 | 339 | // this stringer case will likely pick up most of the rest 340 | switch elif.(type) { 341 | case fmt.Stringer: 342 | sort.Slice(keys, func(i, j int) bool { 343 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).Interface().(fmt.Stringer).String() 344 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).Interface().(fmt.Stringer).String() 345 | if ascending { 346 | return iv < jv 347 | } 348 | return iv > jv 349 | }) 350 | return nil 351 | } 352 | 353 | // last resort! 354 | switch { 355 | case vk == reflect.String: 356 | sort.Slice(keys, func(i, j int) bool { 357 | iv := NonPtrValue(mpvnp.MapIndex(keys[i])).String() 358 | jv := NonPtrValue(mpvnp.MapIndex(keys[j])).String() 359 | if ascending { 360 | return strings.ToLower(iv) < strings.ToLower(jv) 361 | } 362 | return strings.ToLower(iv) > strings.ToLower(jv) 363 | }) 364 | return nil 365 | } 366 | 367 | err := fmt.Errorf("MapValueSort: unable to sort elements of type: %v", eltyp.String()) 368 | log.Println(err) 369 | return err 370 | } 371 | 372 | // CopyMapRobust robustly copies maps using SetRobust method for the elements. 373 | func CopyMapRobust(to, fm any) error { 374 | tov := reflect.ValueOf(to) 375 | fmv := reflect.ValueOf(fm) 376 | tonp := NonPtrValue(tov) 377 | fmnp := NonPtrValue(fmv) 378 | totyp := tonp.Type() 379 | if totyp.Kind() != reflect.Map { 380 | err := fmt.Errorf("ki.CopyMapRobust: 'to' is not map, is: %v", totyp.String()) 381 | log.Println(err) 382 | return err 383 | } 384 | fmtyp := fmnp.Type() 385 | if fmtyp.Kind() != reflect.Map { 386 | err := fmt.Errorf("ki.CopyMapRobust: 'from' is not map, is: %v", fmtyp.String()) 387 | log.Println(err) 388 | return err 389 | } 390 | if tonp.IsNil() { 391 | OnePtrValue(tov).Elem().Set(MakeMap(totyp).Elem()) 392 | } else { 393 | MapDeleteAll(to) 394 | } 395 | if fmnp.Len() == 0 { 396 | return nil 397 | } 398 | eltyp := SliceElType(to) 399 | itr := fmnp.MapRange() 400 | for itr.Next() { 401 | tonp.SetMapIndex(itr.Key(), CloneToType(eltyp, itr.Value().Interface()).Elem()) 402 | } 403 | return nil 404 | } 405 | -------------------------------------------------------------------------------- /kit/maps_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | // test map type functions 12 | func TestMapType(t *testing.T) { 13 | var mp map[string]int 14 | 15 | ts := MapValueType(mp).String() 16 | if ts != "int" { 17 | t.Errorf("map val type should be int, not: %v\n", ts) 18 | } 19 | 20 | ts = MapValueType(&mp).String() 21 | if ts != "int" { 22 | t.Errorf("map val type should be int, not: %v\n", ts) 23 | } 24 | 25 | ts = MapKeyType(mp).String() 26 | if ts != "string" { 27 | t.Errorf("map key type should be string, not: %v\n", ts) 28 | } 29 | 30 | ts = MapKeyType(&mp).String() 31 | if ts != "string" { 32 | t.Errorf("map key type should be string, not: %v\n", ts) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /kit/ptrs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "reflect" 9 | ) 10 | 11 | // These are a set of consistently-named functions for navigating pointer 12 | // types and values within the reflect system 13 | 14 | ///////////////////////////////////////////////// 15 | // reflect.Type versions 16 | 17 | // NonPtrType returns the non-pointer underlying type 18 | func NonPtrType(typ reflect.Type) reflect.Type { 19 | if typ == nil { 20 | return typ 21 | } 22 | for typ.Kind() == reflect.Ptr { 23 | typ = typ.Elem() 24 | } 25 | return typ 26 | } 27 | 28 | // PtrType returns the pointer type for given type, if given type is not already a Ptr 29 | func PtrType(typ reflect.Type) reflect.Type { 30 | if typ == nil { 31 | return typ 32 | } 33 | if typ.Kind() != reflect.Ptr { 34 | typ = reflect.PtrTo(typ) 35 | } 36 | return typ 37 | } 38 | 39 | // OnePtrType returns a type that is exactly one pointer away from a non-pointer type 40 | func OnePtrType(typ reflect.Type) reflect.Type { 41 | if typ == nil { 42 | return typ 43 | } 44 | if typ.Kind() != reflect.Ptr { 45 | typ = reflect.PtrTo(typ) 46 | } else { 47 | for typ.Elem().Kind() == reflect.Ptr { 48 | typ = typ.Elem() 49 | } 50 | } 51 | return typ 52 | } 53 | 54 | ///////////////////////////////////////////////// 55 | // reflect.Value versions 56 | 57 | // NonPtrValue returns the non-pointer underlying value 58 | func NonPtrValue(v reflect.Value) reflect.Value { 59 | for v.Kind() == reflect.Ptr { 60 | v = v.Elem() 61 | } 62 | return v 63 | } 64 | 65 | // PtrValue returns the pointer version (Addr()) of the underlying value if 66 | // the value is not already a Ptr 67 | func PtrValue(v reflect.Value) reflect.Value { 68 | if v.CanAddr() && v.Kind() != reflect.Ptr { 69 | v = v.Addr() 70 | } 71 | return v 72 | } 73 | 74 | // OnePtrValue returns a value that is exactly one pointer away 75 | // from a non-pointer type 76 | func OnePtrValue(v reflect.Value) reflect.Value { 77 | if v.Kind() != reflect.Ptr { 78 | if v.CanAddr() { 79 | v = v.Addr() 80 | } 81 | } else { 82 | for v.Elem().Kind() == reflect.Ptr { 83 | v = v.Elem() 84 | } 85 | } 86 | return v 87 | } 88 | 89 | // OnePtrUnderlyingValue returns a value that is exactly one pointer away 90 | // from a non-pointer type, and also goes through an interface to find the 91 | // actual underlying type behind the interface. 92 | func OnePtrUnderlyingValue(v reflect.Value) reflect.Value { 93 | opv := OnePtrValue(v) 94 | npv := NonPtrValue(opv) 95 | itv := reflect.ValueOf(npv.Interface()) 96 | if itv.Kind() == reflect.Ptr { 97 | // for this to still be a ptr, means that orig Value is 98 | // an interface, so this is the underlying value of that interface 99 | opv = OnePtrValue(itv) 100 | } 101 | return opv 102 | } 103 | 104 | // MakePtrValue makes a new pointer to the given value, adding an extra level 105 | // of indirection, and then removing that indirection, resulting in something 106 | // that is now addressable / assignable -- this is necessary for enums.. 107 | func MakePtrValue(v reflect.Value) reflect.Value { 108 | np := reflect.New(PtrType(v.Type())) 109 | pi := np.Interface() 110 | pi = v.Interface() // assign pointer using interface assignment instead of set.. 111 | p := reflect.ValueOf(pi) // has a double pointer, remove that last one 112 | return p.Elem() 113 | } 114 | 115 | // UnhideIfaceValue returns a reflect.Value for any of the Make* functions 116 | // that is actually assignable -- even though these functions return a pointer 117 | // to the new object, it is somehow hidden behind an interface{} and this 118 | // magic code, posted by someone somewhere that I cannot now find again, 119 | // un-hides it.. 120 | func UnhideIfaceValue(v reflect.Value) reflect.Value { 121 | vn := reflect.ValueOf(v.Interface()) 122 | typ := vn.Type() 123 | ptr := reflect.New(typ) 124 | ptr.Elem().Set(vn) 125 | return ptr 126 | } 127 | 128 | ///////////////////////////////////////////////// 129 | // interface{} versions 130 | 131 | // NonPtrInterface returns the non-pointer value of an interface 132 | func NonPtrInterface(el any) any { 133 | v := reflect.ValueOf(el) 134 | for v.Kind() == reflect.Ptr { 135 | v = v.Elem() 136 | } 137 | return v.Interface() 138 | } 139 | 140 | // PtrInterface returns the pointer value of an interface, if it is possible to get one through Addr() 141 | func PtrInterface(el any) any { 142 | v := reflect.ValueOf(el) 143 | if v.Kind() == reflect.Ptr { 144 | return el 145 | } 146 | if v.CanAddr() { 147 | return v.Addr().Interface() 148 | } 149 | return el 150 | } 151 | 152 | // OnePtrInterface returns the pointer value of an interface, if it is possible to get one through Addr() 153 | func OnePtrInterface(el any) any { 154 | return OnePtrValue(reflect.ValueOf(el)).Interface() 155 | } 156 | -------------------------------------------------------------------------------- /kit/ptrs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | "unsafe" 11 | ) 12 | 13 | // structs are in embeds_test.go 14 | 15 | type PtrTstSub struct { 16 | Mbr1 string 17 | Mbr2 int 18 | Enum TestFlags 19 | } 20 | 21 | type PtrTst struct { 22 | Mbr1 string 23 | Mbr2 int 24 | Enum TestFlags 25 | SubField PtrTstSub 26 | } 27 | 28 | var pt = PtrTst{} 29 | 30 | func InitPtrTst() { 31 | pt.Mbr1 = "mbr1 string" 32 | pt.Mbr2 = 2 33 | pt.Enum = TestFlag2 34 | } 35 | 36 | func FieldValue(obj any, fld reflect.StructField) reflect.Value { 37 | ov := reflect.ValueOf(obj) 38 | f := unsafe.Pointer(ov.Pointer() + fld.Offset) 39 | nw := reflect.NewAt(fld.Type, f) 40 | return UnhideIfaceValue(nw).Elem() 41 | } 42 | 43 | func SubFieldValue(obj any, fld reflect.StructField, sub reflect.StructField) reflect.Value { 44 | ov := reflect.ValueOf(obj) 45 | f := unsafe.Pointer(ov.Pointer() + fld.Offset + sub.Offset) 46 | nw := reflect.NewAt(sub.Type, f) 47 | return UnhideIfaceValue(nw).Elem() 48 | } 49 | 50 | // test ability to create an addressable pointer value to fields of a struct 51 | func TestNewAt(t *testing.T) { 52 | InitPtrTst() 53 | typ := reflect.TypeOf(pt) 54 | fld, _ := typ.FieldByName("Mbr2") 55 | vf := FieldValue(&pt, fld) 56 | 57 | // fmt.Printf("Fld: %v Typ: %v vf: %v vfi: %v vfT: %v vfp: %v canaddr: %v canset: %v caninterface: %v\n", fld.Name, vf.Type().String(), vf.String(), vf.Interface(), vf.Interface(), vf.Interface(), vf.CanAddr(), vf.CanSet(), vf.CanInterface()) 58 | 59 | vf.Elem().Set(reflect.ValueOf(int(10))) 60 | 61 | if pt.Mbr2 != 10 { 62 | t.Errorf("Mbr2 should be 10, is: %v\n", pt.Mbr2) 63 | } 64 | 65 | fld, _ = typ.FieldByName("Mbr1") 66 | vf = FieldValue(&pt, fld) 67 | 68 | // fmt.Printf("Fld: %v Typ: %v vf: %v vfi: %v vfT: %v vfp: %v canaddr: %v canset: %v caninterface: %v\n", fld.Name, vf.Type().String(), vf.String(), vf.Interface(), vf.Interface(), vf.Interface(), vf.CanAddr(), vf.CanSet(), vf.CanInterface()) 69 | 70 | vf.Elem().Set(reflect.ValueOf("this is a new string")) 71 | 72 | if pt.Mbr1 != "this is a new string" { 73 | t.Errorf("Mbr1 should be 'this is a new string': %v\n", pt.Mbr1) 74 | } 75 | 76 | fld, _ = typ.FieldByName("Enum") 77 | vf = FieldValue(&pt, fld) 78 | 79 | // fmt.Printf("Fld: %v Typ: %v vf: %v vfi: %v vfT: %v vfp: %v canaddr: %v canset: %v caninterface: %v\n", fld.Name, vf.Type().String(), vf.String(), vf.Interface(), vf.Interface(), vf.Interface(), vf.CanAddr(), vf.CanSet(), vf.CanInterface()) 80 | 81 | vf.Elem().Set(reflect.ValueOf(TestFlag1)) 82 | 83 | if pt.Enum != TestFlag1 { 84 | t.Errorf("Enum should be TestFlag1: %v\n", pt.Enum) 85 | } 86 | 87 | err := SetEnumValueFromString(vf, "TestFlag2") 88 | if err != nil { 89 | t.Errorf("%v", err) 90 | } 91 | 92 | if pt.Enum != TestFlag2 { 93 | t.Errorf("Enum should be TestFlag2: %v\n", pt.Enum) 94 | } 95 | 96 | err = Enums.SetEnumValueFromAltString(vf, "flag1") 97 | if err != nil { 98 | t.Errorf("%v", err) 99 | } 100 | 101 | if pt.Enum != TestFlag1 { 102 | t.Errorf("Enum should be TestFlag1: %v\n", pt.Enum) 103 | } 104 | } 105 | 106 | func TestNewAtSub(t *testing.T) { 107 | InitPtrTst() 108 | typ := reflect.TypeOf(pt) 109 | subtyp := reflect.TypeOf(pt.SubField) 110 | 111 | fld, _ := typ.FieldByName("SubField") 112 | sub, _ := subtyp.FieldByName("Enum") 113 | vf := SubFieldValue(&pt, fld, sub) 114 | 115 | // fmt.Printf("Fld: %v Typ: %v vf: %v vfi: %v vfT: %v vfp: %v canaddr: %v canset: %v caninterface: %v\n", fld.Name, vf.Type().String(), vf.String(), vf.Interface(), vf.Interface(), vf.Interface(), vf.CanAddr(), vf.CanSet(), vf.CanInterface()) 116 | 117 | pt.SubField.Enum = TestFlag2 118 | vf.Elem().Set(reflect.ValueOf(TestFlag1)) 119 | 120 | if pt.SubField.Enum != TestFlag1 { 121 | t.Errorf("Enum should be TestFlag1: %v\n", pt.SubField.Enum) 122 | } 123 | 124 | err := SetEnumValueFromString(vf, "TestFlag2") 125 | if err != nil { 126 | t.Errorf("%v", err) 127 | } 128 | 129 | if pt.SubField.Enum != TestFlag2 { 130 | t.Errorf("Enum should be TestFlag2: %v\n", pt.SubField.Enum) 131 | } 132 | 133 | err = Enums.SetEnumValueFromAltString(vf, "flag1") 134 | if err != nil { 135 | t.Errorf("%v", err) 136 | } 137 | 138 | if pt.SubField.Enum != TestFlag1 { 139 | t.Errorf("Enum should be TestFlag1: %v\n", pt.SubField.Enum) 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /kit/slices.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "reflect" 11 | "sort" 12 | "strings" 13 | "time" 14 | 15 | "github.com/goki/ki/floats" 16 | "github.com/goki/ki/ints" 17 | ) 18 | 19 | // This file contains helpful functions for dealing with slices, in the reflect 20 | // system 21 | 22 | // MakeSlice makes a map that is actually addressable, getting around the hidden 23 | // interface{} that reflect.MakeSlice makes, by calling UnhideIfaceValue (from ptrs.go) 24 | func MakeSlice(typ reflect.Type, len, cap int) reflect.Value { 25 | return UnhideIfaceValue(reflect.MakeSlice(typ, len, cap)) 26 | } 27 | 28 | // SliceElType returns the type of the elements for the given slice (which can be 29 | // a pointer to a slice or a direct slice) -- just Elem() of slice type, but using 30 | // this function makes it more explicit what is going on. And it uses 31 | // OnePtrUnderlyingValue to get past any interface wrapping. 32 | func SliceElType(sl any) reflect.Type { 33 | return NonPtrValue(OnePtrUnderlyingValue(reflect.ValueOf(sl))).Type().Elem() 34 | } 35 | 36 | // SliceNewAt inserts a new blank element at given index in the slice -- -1 37 | // means the end 38 | func SliceNewAt(sl any, idx int) { 39 | sltyp := SliceElType(sl) 40 | slptr := sltyp.Kind() == reflect.Ptr 41 | 42 | svl := reflect.ValueOf(sl) 43 | svnp := NonPtrValue(svl) 44 | 45 | nval := reflect.New(NonPtrType(sltyp)) // make the concrete el 46 | if !slptr { 47 | nval = nval.Elem() // use concrete value 48 | } 49 | sz := svnp.Len() 50 | svnp = reflect.Append(svnp, nval) 51 | if idx >= 0 && idx < sz { 52 | reflect.Copy(svnp.Slice(idx+1, sz+1), svnp.Slice(idx, sz)) 53 | svnp.Index(idx).Set(nval) 54 | } 55 | svl.Elem().Set(svnp) 56 | } 57 | 58 | // SliceDeleteAt deletes element at given index from slice 59 | func SliceDeleteAt(sl any, idx int) { 60 | svl := reflect.ValueOf(sl) 61 | svnp := NonPtrValue(svl) 62 | svtyp := svnp.Type() 63 | nval := reflect.New(svtyp.Elem()) 64 | sz := svnp.Len() 65 | reflect.Copy(svnp.Slice(idx, sz-1), svnp.Slice(idx+1, sz)) 66 | svnp.Index(sz - 1).Set(nval.Elem()) 67 | svl.Elem().Set(svnp.Slice(0, sz-1)) 68 | } 69 | 70 | // SliceSort sorts a slice of basic values (see StructSliceSort for sorting a 71 | // slice-of-struct using a specific field), trying floats.Floater Float(), 72 | // ints.Inter Int(), interfaces first, and then falling back on reflect.Kind 73 | // float, int, string conversions (first fmt.Stringer String()) and supporting 74 | // time.Time directly as well. 75 | func SliceSort(sl any, ascending bool) error { 76 | sv := reflect.ValueOf(sl) 77 | svnp := NonPtrValue(sv) 78 | if svnp.Len() == 0 { 79 | return nil 80 | } 81 | eltyp := SliceElType(sl) 82 | elnptyp := NonPtrType(eltyp) 83 | vk := elnptyp.Kind() 84 | elval := OnePtrValue(svnp.Index(0)) 85 | elif := elval.Interface() 86 | 87 | switch elif.(type) { 88 | case floats.Floater: 89 | sort.Slice(svnp.Interface(), func(i, j int) bool { 90 | iv := NonPtrValue(svnp.Index(i)).Interface().(floats.Floater).Float() 91 | jv := NonPtrValue(svnp.Index(j)).Interface().(floats.Floater).Float() 92 | if ascending { 93 | return iv < jv 94 | } 95 | return iv > jv 96 | }) 97 | return nil 98 | case ints.Inter: 99 | sort.Slice(svnp.Interface(), func(i, j int) bool { 100 | iv := NonPtrValue(svnp.Index(i)).Interface().(ints.Inter).Int() 101 | jv := NonPtrValue(svnp.Index(j)).Interface().(ints.Inter).Int() 102 | if ascending { 103 | return iv < jv 104 | } 105 | return iv > jv 106 | }) 107 | return nil 108 | } 109 | 110 | // try all the numeric types first! 111 | 112 | switch { 113 | case vk >= reflect.Int && vk <= reflect.Int64: 114 | sort.Slice(svnp.Interface(), func(i, j int) bool { 115 | iv := NonPtrValue(svnp.Index(i)).Int() 116 | jv := NonPtrValue(svnp.Index(j)).Int() 117 | if ascending { 118 | return iv < jv 119 | } 120 | return iv > jv 121 | }) 122 | return nil 123 | case vk >= reflect.Uint && vk <= reflect.Uint64: 124 | sort.Slice(svnp.Interface(), func(i, j int) bool { 125 | iv := NonPtrValue(svnp.Index(i)).Uint() 126 | jv := NonPtrValue(svnp.Index(j)).Uint() 127 | if ascending { 128 | return iv < jv 129 | } 130 | return iv > jv 131 | }) 132 | return nil 133 | case vk >= reflect.Float32 && vk <= reflect.Float64: 134 | sort.Slice(svnp.Interface(), func(i, j int) bool { 135 | iv := NonPtrValue(svnp.Index(i)).Float() 136 | jv := NonPtrValue(svnp.Index(j)).Float() 137 | if ascending { 138 | return iv < jv 139 | } 140 | return iv > jv 141 | }) 142 | return nil 143 | case vk == reflect.Struct && ShortTypeName(elnptyp) == "time.Time": 144 | sort.Slice(svnp.Interface(), func(i, j int) bool { 145 | iv := NonPtrValue(svnp.Index(i)).Interface().(time.Time) 146 | jv := NonPtrValue(svnp.Index(j)).Interface().(time.Time) 147 | if ascending { 148 | return iv.Before(jv) 149 | } 150 | return jv.Before(iv) 151 | }) 152 | } 153 | 154 | // this stringer case will likely pick up most of the rest 155 | switch elif.(type) { 156 | case fmt.Stringer: 157 | sort.Slice(svnp.Interface(), func(i, j int) bool { 158 | iv := NonPtrValue(svnp.Index(i)).Interface().(fmt.Stringer).String() 159 | jv := NonPtrValue(svnp.Index(j)).Interface().(fmt.Stringer).String() 160 | if ascending { 161 | return iv < jv 162 | } 163 | return iv > jv 164 | }) 165 | return nil 166 | } 167 | 168 | // last resort! 169 | switch { 170 | case vk == reflect.String: 171 | sort.Slice(svnp.Interface(), func(i, j int) bool { 172 | iv := NonPtrValue(svnp.Index(i)).String() 173 | jv := NonPtrValue(svnp.Index(j)).String() 174 | if ascending { 175 | return strings.ToLower(iv) < strings.ToLower(jv) 176 | } 177 | return strings.ToLower(iv) > strings.ToLower(jv) 178 | }) 179 | return nil 180 | } 181 | 182 | err := fmt.Errorf("SortSlice: unable to sort elements of type: %v", eltyp.String()) 183 | log.Println(err) 184 | return err 185 | } 186 | 187 | // StructSliceSort sorts a slice of a struct according to the given field 188 | // indexes and sort direction, trying floats.Floater Float(), ints.Inter Int(), 189 | // interfaces first, and then falling back on reflect.Kind float, int, string 190 | // conversions (first fmt.Stringer String()) and supporting time.Time directly 191 | // as well. There is no direct method for checking the field indexes so those 192 | // are assumed to be accurate -- will panic if not! 193 | func StructSliceSort(struSlice any, fldIdx []int, ascending bool) error { 194 | sv := reflect.ValueOf(struSlice) 195 | svnp := NonPtrValue(sv) 196 | if svnp.Len() == 0 { 197 | return nil 198 | } 199 | struTyp := SliceElType(struSlice) 200 | struNpTyp := NonPtrType(struTyp) 201 | fld := struNpTyp.FieldByIndex(fldIdx) // not easy to check. 202 | vk := fld.Type.Kind() 203 | struVal := OnePtrValue(svnp.Index(0)) 204 | fldVal := struVal.Elem().FieldByIndex(fldIdx) 205 | fldIf := fldVal.Interface() 206 | 207 | switch fldIf.(type) { 208 | case floats.Floater: 209 | sort.Slice(svnp.Interface(), func(i, j int) bool { 210 | ival := OnePtrValue(svnp.Index(i)) 211 | iv := ival.Elem().FieldByIndex(fldIdx).Interface().(floats.Floater).Float() 212 | jval := OnePtrValue(svnp.Index(j)) 213 | jv := jval.Elem().FieldByIndex(fldIdx).Interface().(floats.Floater).Float() 214 | if ascending { 215 | return iv < jv 216 | } 217 | return iv > jv 218 | }) 219 | return nil 220 | case ints.Inter: 221 | sort.Slice(svnp.Interface(), func(i, j int) bool { 222 | ival := OnePtrValue(svnp.Index(i)) 223 | iv := ival.Elem().FieldByIndex(fldIdx).Interface().(ints.Inter).Int() 224 | jval := OnePtrValue(svnp.Index(j)) 225 | jv := jval.Elem().FieldByIndex(fldIdx).Interface().(ints.Inter).Int() 226 | if ascending { 227 | return iv < jv 228 | } 229 | return iv > jv 230 | }) 231 | return nil 232 | } 233 | 234 | // try all the numeric types first! 235 | 236 | switch { 237 | case vk >= reflect.Int && vk <= reflect.Int64: 238 | sort.Slice(svnp.Interface(), func(i, j int) bool { 239 | ival := OnePtrValue(svnp.Index(i)) 240 | iv := ival.Elem().FieldByIndex(fldIdx).Int() 241 | jval := OnePtrValue(svnp.Index(j)) 242 | jv := jval.Elem().FieldByIndex(fldIdx).Int() 243 | if ascending { 244 | return iv < jv 245 | } 246 | return iv > jv 247 | }) 248 | return nil 249 | case vk >= reflect.Uint && vk <= reflect.Uint64: 250 | sort.Slice(svnp.Interface(), func(i, j int) bool { 251 | ival := OnePtrValue(svnp.Index(i)) 252 | iv := ival.Elem().FieldByIndex(fldIdx).Uint() 253 | jval := OnePtrValue(svnp.Index(j)) 254 | jv := jval.Elem().FieldByIndex(fldIdx).Uint() 255 | if ascending { 256 | return iv < jv 257 | } 258 | return iv > jv 259 | }) 260 | return nil 261 | case vk >= reflect.Float32 && vk <= reflect.Float64: 262 | sort.Slice(svnp.Interface(), func(i, j int) bool { 263 | ival := OnePtrValue(svnp.Index(i)) 264 | iv := ival.Elem().FieldByIndex(fldIdx).Float() 265 | jval := OnePtrValue(svnp.Index(j)) 266 | jv := jval.Elem().FieldByIndex(fldIdx).Float() 267 | if ascending { 268 | return iv < jv 269 | } 270 | return iv > jv 271 | }) 272 | return nil 273 | case vk == reflect.Struct && ShortTypeName(fld.Type) == "time.Time": 274 | sort.Slice(svnp.Interface(), func(i, j int) bool { 275 | ival := OnePtrValue(svnp.Index(i)) 276 | iv := ival.Elem().FieldByIndex(fldIdx).Interface().(time.Time) 277 | jval := OnePtrValue(svnp.Index(j)) 278 | jv := jval.Elem().FieldByIndex(fldIdx).Interface().(time.Time) 279 | if ascending { 280 | return iv.Before(jv) 281 | } 282 | return jv.Before(iv) 283 | }) 284 | } 285 | 286 | // this stringer case will likely pick up most of the rest 287 | switch fldIf.(type) { 288 | case fmt.Stringer: 289 | sort.Slice(svnp.Interface(), func(i, j int) bool { 290 | ival := OnePtrValue(svnp.Index(i)) 291 | iv := ival.Elem().FieldByIndex(fldIdx).Interface().(fmt.Stringer).String() 292 | jval := OnePtrValue(svnp.Index(j)) 293 | jv := jval.Elem().FieldByIndex(fldIdx).Interface().(fmt.Stringer).String() 294 | if ascending { 295 | return iv < jv 296 | } 297 | return iv > jv 298 | }) 299 | return nil 300 | } 301 | 302 | // last resort! 303 | switch { 304 | case vk == reflect.String: 305 | sort.Slice(svnp.Interface(), func(i, j int) bool { 306 | ival := OnePtrValue(svnp.Index(i)) 307 | iv := ival.Elem().FieldByIndex(fldIdx).String() 308 | jval := OnePtrValue(svnp.Index(j)) 309 | jv := jval.Elem().FieldByIndex(fldIdx).String() 310 | if ascending { 311 | return strings.ToLower(iv) < strings.ToLower(jv) 312 | } 313 | return strings.ToLower(iv) > strings.ToLower(jv) 314 | }) 315 | return nil 316 | } 317 | 318 | err := fmt.Errorf("SortStructSlice: unable to sort on field of type: %v\n", fld.Type.String()) 319 | log.Println(err) 320 | return err 321 | } 322 | 323 | // ValueSliceSort sorts a slice of reflect.Values using basic types where possible 324 | func ValueSliceSort(sl []reflect.Value, ascending bool) error { 325 | if len(sl) == 0 { 326 | return nil 327 | } 328 | felval := sl[0] // reflect.Value 329 | eltyp := felval.Type() 330 | elnptyp := NonPtrType(eltyp) 331 | vk := elnptyp.Kind() 332 | elval := OnePtrValue(felval) 333 | elif := elval.Interface() 334 | 335 | switch elif.(type) { 336 | case floats.Floater: 337 | sort.Slice(sl, func(i, j int) bool { 338 | iv := NonPtrValue(sl[i]).Interface().(floats.Floater).Float() 339 | jv := NonPtrValue(sl[j]).Interface().(floats.Floater).Float() 340 | if ascending { 341 | return iv < jv 342 | } 343 | return iv > jv 344 | }) 345 | return nil 346 | case ints.Inter: 347 | sort.Slice(sl, func(i, j int) bool { 348 | iv := NonPtrValue(sl[i]).Interface().(ints.Inter).Int() 349 | jv := NonPtrValue(sl[j]).Interface().(ints.Inter).Int() 350 | if ascending { 351 | return iv < jv 352 | } 353 | return iv > jv 354 | }) 355 | return nil 356 | } 357 | 358 | // try all the numeric types first! 359 | 360 | switch { 361 | case vk >= reflect.Int && vk <= reflect.Int64: 362 | sort.Slice(sl, func(i, j int) bool { 363 | iv := NonPtrValue(sl[i]).Int() 364 | jv := NonPtrValue(sl[j]).Int() 365 | if ascending { 366 | return iv < jv 367 | } 368 | return iv > jv 369 | }) 370 | return nil 371 | case vk >= reflect.Uint && vk <= reflect.Uint64: 372 | sort.Slice(sl, func(i, j int) bool { 373 | iv := NonPtrValue(sl[i]).Uint() 374 | jv := NonPtrValue(sl[j]).Uint() 375 | if ascending { 376 | return iv < jv 377 | } 378 | return iv > jv 379 | }) 380 | return nil 381 | case vk >= reflect.Float32 && vk <= reflect.Float64: 382 | sort.Slice(sl, func(i, j int) bool { 383 | iv := NonPtrValue(sl[i]).Float() 384 | jv := NonPtrValue(sl[j]).Float() 385 | if ascending { 386 | return iv < jv 387 | } 388 | return iv > jv 389 | }) 390 | return nil 391 | case vk == reflect.Struct && ShortTypeName(elnptyp) == "time.Time": 392 | sort.Slice(sl, func(i, j int) bool { 393 | iv := NonPtrValue(sl[i]).Interface().(time.Time) 394 | jv := NonPtrValue(sl[j]).Interface().(time.Time) 395 | if ascending { 396 | return iv.Before(jv) 397 | } 398 | return jv.Before(iv) 399 | }) 400 | } 401 | 402 | // this stringer case will likely pick up most of the rest 403 | switch elif.(type) { 404 | case fmt.Stringer: 405 | sort.Slice(sl, func(i, j int) bool { 406 | iv := NonPtrValue(sl[i]).Interface().(fmt.Stringer).String() 407 | jv := NonPtrValue(sl[j]).Interface().(fmt.Stringer).String() 408 | if ascending { 409 | return iv < jv 410 | } 411 | return iv > jv 412 | }) 413 | return nil 414 | } 415 | 416 | // last resort! 417 | switch { 418 | case vk == reflect.String: 419 | sort.Slice(sl, func(i, j int) bool { 420 | iv := NonPtrValue(sl[i]).String() 421 | jv := NonPtrValue(sl[j]).String() 422 | if ascending { 423 | return strings.ToLower(iv) < strings.ToLower(jv) 424 | } 425 | return strings.ToLower(iv) > strings.ToLower(jv) 426 | }) 427 | return nil 428 | } 429 | 430 | err := fmt.Errorf("ValueSliceSort: unable to sort elements of type: %v", eltyp.String()) 431 | log.Println(err) 432 | return err 433 | } 434 | 435 | // CopySliceRobust robustly copies slices using SetRobust method for the elements. 436 | func CopySliceRobust(to, fm any) error { 437 | tov := reflect.ValueOf(to) 438 | fmv := reflect.ValueOf(fm) 439 | tonp := NonPtrValue(tov) 440 | fmnp := NonPtrValue(fmv) 441 | totyp := tonp.Type() 442 | // eltyp := SliceElType(tonp) 443 | if totyp.Kind() != reflect.Slice { 444 | err := fmt.Errorf("ki.CopySliceRobust: 'to' is not slice, is: %v", totyp.String()) 445 | log.Println(err) 446 | return err 447 | } 448 | fmtyp := fmnp.Type() 449 | if fmtyp.Kind() != reflect.Slice { 450 | err := fmt.Errorf("ki.CopySliceRobust: 'from' is not slice, is: %v", fmtyp.String()) 451 | log.Println(err) 452 | return err 453 | } 454 | fmlen := fmnp.Len() 455 | if tonp.IsNil() { 456 | OnePtrValue(tonp).Elem().Set(MakeSlice(totyp, fmlen, fmlen).Elem()) 457 | } else { 458 | if tonp.Len() > fmlen { 459 | tonp.SetLen(fmlen) 460 | } 461 | } 462 | for i := 0; i < fmlen; i++ { 463 | tolen := tonp.Len() 464 | if i >= tolen { 465 | SliceNewAt(to, i) 466 | } 467 | SetRobust(PtrValue(tonp.Index(i)).Interface(), fmnp.Index(i).Interface()) 468 | } 469 | return nil 470 | } 471 | -------------------------------------------------------------------------------- /kit/slices_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | type TstStruct struct { 12 | Field1 string 13 | } 14 | 15 | // test slice type functions 16 | func TestSliceType(t *testing.T) { 17 | var sl []string 18 | 19 | ts := SliceElType(sl).String() 20 | if ts != "string" { 21 | t.Errorf("slice el type should be string, not: %v\n", ts) 22 | } 23 | 24 | ts = SliceElType(&sl).String() 25 | if ts != "string" { 26 | t.Errorf("slice el type should be string, not: %v\n", ts) 27 | } 28 | 29 | var slp []*string 30 | 31 | ts = SliceElType(slp).String() 32 | if ts != "*string" { 33 | t.Errorf("slice el type should be *string, not: %v\n", ts) 34 | } 35 | 36 | ts = SliceElType(&slp).String() 37 | if ts != "*string" { 38 | t.Errorf("slice el type should be *string, not: %v\n", ts) 39 | } 40 | 41 | var slsl [][]string 42 | 43 | ts = SliceElType(slsl).String() 44 | if ts != "[]string" { 45 | t.Errorf("slice el type should be []string, not: %v\n", ts) 46 | } 47 | 48 | ts = SliceElType(&slsl).String() 49 | if ts != "[]string" { 50 | t.Errorf("slice el type should be []string, not: %v\n", ts) 51 | } 52 | 53 | // fmt.Printf("slsl kind: %v\n", SliceElType(slsl).Kind()) 54 | 55 | } 56 | -------------------------------------------------------------------------------- /kit/structs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "reflect" 11 | "strings" 12 | ) 13 | 14 | // SetFromDefaultTags sets values of fields in given struct based on 15 | // `def:` default value field tags. 16 | func SetFromDefaultTags(obj any) error { 17 | if IfaceIsNil(obj) { 18 | return nil 19 | } 20 | ov := reflect.ValueOf(obj) 21 | if ov.Kind() == reflect.Pointer && ov.IsNil() { 22 | return nil 23 | } 24 | val := NonPtrValue(ov) 25 | typ := val.Type() 26 | var err error 27 | for i := 0; i < typ.NumField(); i++ { 28 | f := typ.Field(i) 29 | fv := val.Field(i) 30 | def, ok := f.Tag.Lookup("def") 31 | if NonPtrType(f.Type).Kind() == reflect.Struct && (!ok || def == "") { 32 | fi := PtrValue(fv).Interface() 33 | if IfaceIsNil(fi) { 34 | continue 35 | } 36 | SetFromDefaultTags(fi) 37 | continue 38 | } 39 | if !ok || def == "" { 40 | continue 41 | } 42 | if def[0] == '{' || def[0] == '[' { // complex type 43 | def = strings.ReplaceAll(def, `'`, `"`) // allow single quote to work as double quote for JSON format 44 | } else { 45 | def = strings.Split(def, ",")[0] 46 | if strings.Contains(def, ":") { // don't do ranges 47 | continue 48 | } 49 | } 50 | ok = SetRobust(PtrValue(fv).Interface(), def) // overkill but whatever 51 | if !ok { 52 | err = fmt.Errorf("SetFromDefaultTags: was not able to set field: %s in object of type: %s from val: %s", f.Name, typ.Name(), def) 53 | log.Println(err) 54 | } 55 | } 56 | return err 57 | } 58 | -------------------------------------------------------------------------------- /kit/testflags_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=TestFlags"; DO NOT EDIT. 2 | 3 | package kit 4 | 5 | import ( 6 | "errors" 7 | "strconv" 8 | ) 9 | 10 | func _() { 11 | // An "invalid array index" compiler error signifies that the constant values have changed. 12 | // Re-run the stringer command to generate them again. 13 | var x [1]struct{} 14 | _ = x[TestFlagsNil-0] 15 | _ = x[TestFlag1-1] 16 | _ = x[TestFlag2-2] 17 | _ = x[TestFlagsN-3] 18 | } 19 | 20 | const _TestFlags_name = "TestFlagsNilTestFlag1TestFlag2TestFlagsN" 21 | 22 | var _TestFlags_index = [...]uint8{0, 12, 21, 30, 40} 23 | 24 | func (i TestFlags) String() string { 25 | if i < 0 || i >= TestFlags(len(_TestFlags_index)-1) { 26 | return "TestFlags(" + strconv.FormatInt(int64(i), 10) + ")" 27 | } 28 | return _TestFlags_name[_TestFlags_index[i]:_TestFlags_index[i+1]] 29 | } 30 | 31 | func (i *TestFlags) FromString(s string) error { 32 | for j := 0; j < len(_TestFlags_index)-1; j++ { 33 | if s == _TestFlags_name[_TestFlags_index[j]:_TestFlags_index[j+1]] { 34 | *i = TestFlags(j) 35 | return nil 36 | } 37 | } 38 | return errors.New("String: " + s + " is not a valid option for type: TestFlags") 39 | } 40 | 41 | var _TestFlags_descMap = map[TestFlags]string{ 42 | 0: ``, 43 | 1: ``, 44 | 2: ``, 45 | 3: ``, 46 | } 47 | 48 | func (i TestFlags) Desc() string { 49 | if str, ok := _TestFlags_descMap[i]; ok { 50 | return str 51 | } 52 | return "TestFlags(" + strconv.FormatInt(int64(i), 10) + ")" 53 | } 54 | -------------------------------------------------------------------------------- /kit/type.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "encoding/xml" 11 | "fmt" 12 | "reflect" 13 | "strings" 14 | ) 15 | 16 | // Type provides JSON, XML marshal / unmarshal with encoding of underlying 17 | // type name using kit.Types type name registry 18 | type Type struct { 19 | T reflect.Type 20 | } 21 | 22 | // ShortTypeName returns short package-qualified name of the type: package dir + "." + type name 23 | func (k Type) ShortTypeName() string { 24 | return Types.TypeName(k.T) 25 | } 26 | 27 | // String satisfies the stringer interface 28 | func String(k Type) string { 29 | if k.T == nil { 30 | return "nil" 31 | } 32 | return k.ShortTypeName() 33 | } 34 | 35 | // MarshalJSON saves only the type name 36 | func (k Type) MarshalJSON() ([]byte, error) { 37 | if k.T == nil { 38 | b := []byte("null") 39 | return b, nil 40 | } 41 | nm := "\"" + k.ShortTypeName() + "\"" 42 | b := []byte(nm) 43 | return b, nil 44 | } 45 | 46 | // UnmarshalJSON loads the type name and looks it up in the Types registry of type names 47 | func (k *Type) UnmarshalJSON(b []byte) error { 48 | if bytes.Equal(b, []byte("null")) { 49 | k.T = nil 50 | return nil 51 | } 52 | tn := string(bytes.Trim(bytes.TrimSpace(b), "\"")) 53 | // fmt.Printf("loading type: %v", tn) 54 | typ := Types.Type(tn) 55 | if typ == nil { 56 | return fmt.Errorf("Type UnmarshalJSON: Types type name not found: %v", tn) 57 | } 58 | k.T = typ 59 | return nil 60 | } 61 | 62 | // todo: try to save info as an attribute within a single element instead of 63 | // full start/end 64 | 65 | // MarshalXML saves only the type name 66 | func (k Type) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 67 | tokens := []xml.Token{start} 68 | if k.T == nil { 69 | tokens = append(tokens, xml.CharData("null")) 70 | } else { 71 | tokens = append(tokens, xml.CharData(k.ShortTypeName())) 72 | } 73 | tokens = append(tokens, xml.EndElement{start.Name}) 74 | for _, t := range tokens { 75 | err := e.EncodeToken(t) 76 | if err != nil { 77 | return err 78 | } 79 | } 80 | err := e.Flush() 81 | if err != nil { 82 | return err 83 | } 84 | return nil 85 | } 86 | 87 | // UnmarshalXML loads the type name and looks it up in the Types registry of type names 88 | func (k *Type) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 89 | t, err := d.Token() 90 | if err != nil { 91 | return err 92 | } 93 | ct, ok := t.(xml.CharData) 94 | if ok { 95 | tn := string(bytes.TrimSpace([]byte(ct))) 96 | if tn == "null" { 97 | k.T = nil 98 | } else { 99 | // fmt.Printf("loading type: %v\n", tn) 100 | typ := Types.Type(tn) 101 | if typ == nil { 102 | return fmt.Errorf("Type UnmarshalXML: Types type name not found: %v", tn) 103 | } 104 | k.T = typ 105 | } 106 | t, err := d.Token() 107 | if err != nil { 108 | return err 109 | } 110 | et, ok := t.(xml.EndElement) 111 | if ok { 112 | if et.Name != start.Name { 113 | return fmt.Errorf("Type UnmarshalXML: EndElement: %v does not match StartElement: %v", et.Name, start.Name) 114 | } 115 | return nil 116 | } 117 | return fmt.Errorf("Type UnmarshalXML: Token: %+v is not expected EndElement", et) 118 | } 119 | return fmt.Errorf("Type UnmarshalXML: Token: %+v is not expected EndElement", ct) 120 | } 121 | 122 | // StructTags returns a map[string]string of the tag string from a reflect.StructTag value 123 | // e.g., from StructField.Tag 124 | func StructTags(tags reflect.StructTag) map[string]string { 125 | if len(tags) == 0 { 126 | return nil 127 | } 128 | flds := strings.Fields(string(tags)) 129 | smap := make(map[string]string, len(flds)) 130 | for _, fld := range flds { 131 | cli := strings.Index(fld, ":") 132 | if cli < 0 || len(fld) < cli+3 { 133 | continue 134 | } 135 | vl := strings.TrimSuffix(fld[cli+2:], `"`) 136 | smap[fld[:cli]] = vl 137 | } 138 | return smap 139 | } 140 | 141 | // StringJSON returns a JSON representation of item, as a string 142 | // e.g., for printing / debugging etc. 143 | func StringJSON(it any) string { 144 | b, _ := json.MarshalIndent(it, "", " ") 145 | return string(b) 146 | } 147 | 148 | // TypeFor returns the [reflect.Type] that represents the type argument T. 149 | // It is a copy of [reflect.TypeFor], which will likely be added in Go 1.22 150 | // (see https://github.com/golang/go/issues/60088) 151 | func TypeFor[T any]() reflect.Type { 152 | return reflect.TypeOf((*T)(nil)).Elem() 153 | } 154 | -------------------------------------------------------------------------------- /kit/typeandname.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package kit 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | // a type and a name -- useful for specifying configurations of children in Ki 14 | // nodes, and various other use-cases 15 | type TypeAndName struct { 16 | Type reflect.Type 17 | Name string 18 | } 19 | 20 | // list of type-and-names -- can be created from a string spec 21 | type TypeAndNameList []TypeAndName 22 | 23 | // construct a type-and-name list from a list of type name pairs, space separated -- can include any json-like { } , [ ] formatting which is all stripped away and just the pairs of names are used 24 | func (t *TypeAndNameList) SetFromString(str string) error { 25 | str = strings.Replace(str, ",", " ", -1) 26 | str = strings.Replace(str, "{", " ", -1) 27 | str = strings.Replace(str, "}", " ", -1) 28 | str = strings.Replace(str, "[", " ", -1) 29 | str = strings.Replace(str, "]", " ", -1) 30 | ds := strings.Fields(str) // split by whitespace 31 | sz := len(ds) 32 | for i := 0; i < sz; i += 2 { 33 | tn := ds[i] 34 | nm := ds[i+1] 35 | typ := Types.Type(tn) 36 | if typ == nil { 37 | return fmt.Errorf("TypeAndNameList SetFromString: Types type name not found: %v", tn) 38 | } 39 | (*t) = append(*t, TypeAndName{typ, nm}) 40 | } 41 | return nil 42 | } 43 | 44 | func (t *TypeAndNameList) Add(typ reflect.Type, nm string) { 45 | (*t) = append(*t, TypeAndName{typ, nm}) 46 | } 47 | -------------------------------------------------------------------------------- /logo/goki_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goki/ki/b954ef959af33a54d94f0afe4927d15f73bad6f3/logo/goki_logo.png -------------------------------------------------------------------------------- /logo/goki_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 37 | 39 | 40 | 42 | image/svg+xml 43 | 45 | 46 | 47 | 48 | 49 | 53 | 60 | 63 | 65 | G 77 | O 89 | 90 | Ki 102 | 107 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /nptime/nptime.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package nptime provides a non-pointer version of the time.Time struct 7 | that does not have the location pointer information that time.Time has, 8 | which is more efficient from a memory management perspective, in cases 9 | where you have a lot of time values being kept: https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/ 10 | */ 11 | package nptime 12 | 13 | import "time" 14 | 15 | // Time represents the value of time.Time without using any pointers for the 16 | // location information, so it is more memory efficient when lots of time 17 | // values are being stored. 18 | type Time struct { 19 | 20 | // time.Time.Unix() seconds since 1970 21 | Sec int64 `desc:"time.Time.Unix() seconds since 1970"` 22 | 23 | // time.Time.Nanosecond() -- nanosecond offset within second, *not* UnixNano() 24 | NSec uint32 `desc:"time.Time.Nanosecond() -- nanosecond offset within second, *not* UnixNano()"` 25 | } 26 | 27 | // TimeZero is the uninitialized zero time value -- use to check whether 28 | // time has been set or not 29 | var TimeZero Time 30 | 31 | // IsZero returns true if the time is zero and has not been initialized 32 | func (t Time) IsZero() bool { 33 | return t == TimeZero 34 | } 35 | 36 | // Time returns the time.Time value for this nptime.Time value 37 | func (t Time) Time() time.Time { 38 | return time.Unix(t.Sec, int64(t.NSec)) 39 | } 40 | 41 | // SetTime sets the nptime.Time value based on the time.Time value 42 | func (t *Time) SetTime(tt time.Time) { 43 | t.Sec = tt.Unix() 44 | t.NSec = uint32(tt.Nanosecond()) 45 | } 46 | 47 | // Now sets the time value to time.Now() 48 | func (t *Time) Now() { 49 | t.SetTime(time.Now()) 50 | } 51 | -------------------------------------------------------------------------------- /nptime/nptime_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package nptime 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestTime(t *testing.T) { 13 | tn := time.Now() 14 | tp := Time{} 15 | tp.SetTime(tn) 16 | tnr := tp.Time() 17 | 18 | if !tn.Equal(tnr) { 19 | t.Errorf("time was not reconstructed properly: %v vs. %v\n", tn, tnr) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /runes/runes.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package runes provides a small subset of functions that are found in strings, bytes 7 | standard packages, for rune slices. For rendering, and other logic, it is best to 8 | keep raw data in runes, and not having to convert back and forth to byte or string 9 | is more efficient. 10 | 11 | These are largely copied from strings or bytes packages. 12 | */ 13 | package runes 14 | 15 | import ( 16 | "unicode" 17 | "unicode/utf8" 18 | ) 19 | 20 | // EqualFold reports whether s and t are equal under Unicode case-folding. 21 | // copied from strings.EqualFold 22 | func EqualFold(s, t []rune) bool { 23 | for len(s) > 0 && len(t) > 0 { 24 | // Extract first rune from each string. 25 | var sr, tr rune 26 | sr, s = s[0], s[1:] 27 | tr, t = t[0], t[1:] 28 | // If they match, keep going; if not, return false. 29 | 30 | // Easy case. 31 | if tr == sr { 32 | continue 33 | } 34 | 35 | // Make sr < tr to simplify what follows. 36 | if tr < sr { 37 | tr, sr = sr, tr 38 | } 39 | // Fast check for ASCII. 40 | if tr < utf8.RuneSelf { 41 | // ASCII only, sr/tr must be upper/lower case 42 | if 'A' <= sr && sr <= 'Z' && tr == sr+'a'-'A' { 43 | continue 44 | } 45 | return false 46 | } 47 | 48 | // General case. SimpleFold(x) returns the next equivalent rune > x 49 | // or wraps around to smaller values. 50 | r := unicode.SimpleFold(sr) 51 | for r != sr && r < tr { 52 | r = unicode.SimpleFold(r) 53 | } 54 | if r == tr { 55 | continue 56 | } 57 | return false 58 | } 59 | 60 | // One string is empty. Are both? 61 | return len(s) == len(t) 62 | } 63 | 64 | // Index returns the index of given rune string in the text, returning -1 if not found. 65 | func Index(txt, find []rune) int { 66 | fsz := len(find) 67 | if fsz == 0 { 68 | return -1 69 | } 70 | tsz := len(txt) 71 | if tsz < fsz { 72 | return -1 73 | } 74 | mn := tsz - fsz 75 | for i := 0; i <= mn; i++ { 76 | found := true 77 | for j := range find { 78 | if txt[i+j] != find[j] { 79 | found = false 80 | break 81 | } 82 | } 83 | if found { 84 | return i 85 | } 86 | } 87 | return -1 88 | } 89 | 90 | // IndexFold returns the index of given rune string in the text, using case folding 91 | // (i.e., case insensitive matching). Returns -1 if not found. 92 | func IndexFold(txt, find []rune) int { 93 | fsz := len(find) 94 | if fsz == 0 { 95 | return -1 96 | } 97 | tsz := len(txt) 98 | if tsz < fsz { 99 | return -1 100 | } 101 | mn := tsz - fsz 102 | for i := 0; i <= mn; i++ { 103 | if EqualFold(txt[i:i+fsz], find) { 104 | return i 105 | } 106 | } 107 | return -1 108 | } 109 | 110 | // Repeat returns a new rune slice consisting of count copies of b. 111 | // 112 | // It panics if count is negative or if 113 | // the result of (len(b) * count) overflows. 114 | func Repeat(r []rune, count int) []rune { 115 | if count == 0 { 116 | return []rune{} 117 | } 118 | // Since we cannot return an error on overflow, 119 | // we should panic if the repeat will generate 120 | // an overflow. 121 | // See Issue golang.org/issue/16237. 122 | if count < 0 { 123 | panic("runes: negative Repeat count") 124 | } else if len(r)*count/count != len(r) { 125 | panic("runes: Repeat count causes overflow") 126 | } 127 | 128 | nb := make([]rune, len(r)*count) 129 | bp := copy(nb, r) 130 | for bp < len(nb) { 131 | copy(nb[bp:], nb[:bp]) 132 | bp *= 2 133 | } 134 | return nb 135 | } 136 | -------------------------------------------------------------------------------- /sliceclone/sliceclone.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package sliceclone provides those basic slice cloning methods that I finally got tired of 7 | rewriting all the time. 8 | */ 9 | package sliceclone 10 | 11 | // String returns a cloned copy of the given string slice -- returns nil 12 | // if slice has zero length 13 | func String(sl []string) []string { 14 | sz := len(sl) 15 | if sz == 0 { 16 | return nil 17 | } 18 | cp := make([]string, sz) 19 | copy(cp, sl) 20 | return cp 21 | } 22 | 23 | // StringExclude returns a cloned copy of the given string slice 24 | // while excluding the list of specific items 25 | func StringExclude(sl, exclude []string) []string { 26 | sz := len(sl) 27 | if sz == 0 { 28 | return nil 29 | } 30 | if len(exclude) == 0 { 31 | return String(sl) 32 | } 33 | var cp []string 34 | for _, s := range sl { 35 | ex := false 36 | for _, e := range exclude { 37 | if e == s { 38 | ex = true 39 | break 40 | } 41 | } 42 | if ex { 43 | continue 44 | } 45 | cp = append(cp, s) 46 | } 47 | return cp 48 | } 49 | 50 | // Byte returns a cloned copy of the given byte slice -- returns nil 51 | // if slice has zero length 52 | func Byte(sl []byte) []byte { 53 | sz := len(sl) 54 | if sz == 0 { 55 | return nil 56 | } 57 | cp := make([]byte, sz) 58 | copy(cp, sl) 59 | return cp 60 | } 61 | 62 | // Rune returns a cloned copy of the given rune slice -- returns nil 63 | // if slice has zero length 64 | func Rune(sl []rune) []rune { 65 | sz := len(sl) 66 | if sz == 0 { 67 | return nil 68 | } 69 | cp := make([]rune, sz) 70 | copy(cp, sl) 71 | return cp 72 | } 73 | 74 | // Bool returns a cloned copy of the given bool slice -- returns nil 75 | // if slice has zero length 76 | func Bool(sl []bool) []bool { 77 | sz := len(sl) 78 | if sz == 0 { 79 | return nil 80 | } 81 | cp := make([]bool, sz) 82 | copy(cp, sl) 83 | return cp 84 | } 85 | 86 | // Int returns a cloned copy of the given int slice -- returns nil 87 | // if slice has zero length 88 | func Int(sl []int) []int { 89 | sz := len(sl) 90 | if sz == 0 { 91 | return nil 92 | } 93 | cp := make([]int, sz) 94 | copy(cp, sl) 95 | return cp 96 | } 97 | 98 | // Int32 returns a cloned copy of the given int32 slice -- returns nil 99 | // if slice has zero length 100 | func Int32(sl []int32) []int32 { 101 | sz := len(sl) 102 | if sz == 0 { 103 | return nil 104 | } 105 | cp := make([]int32, sz) 106 | copy(cp, sl) 107 | return cp 108 | } 109 | 110 | // Int64 returns a cloned copy of the given int64 slice -- returns nil 111 | // if slice has zero length 112 | func Int64(sl []int64) []int64 { 113 | sz := len(sl) 114 | if sz == 0 { 115 | return nil 116 | } 117 | cp := make([]int64, sz) 118 | copy(cp, sl) 119 | return cp 120 | } 121 | 122 | // Float64 returns a cloned copy of the given float64 slice -- returns nil 123 | // if slice has zero length 124 | func Float64(sl []float64) []float64 { 125 | sz := len(sl) 126 | if sz == 0 { 127 | return nil 128 | } 129 | cp := make([]float64, sz) 130 | copy(cp, sl) 131 | return cp 132 | } 133 | 134 | // Float32 returns a cloned copy of the given float32 slice -- returns nil 135 | // if slice has zero length 136 | func Float32(sl []float32) []float32 { 137 | sz := len(sl) 138 | if sz == 0 { 139 | return nil 140 | } 141 | cp := make([]float32, sz) 142 | copy(cp, sl) 143 | return cp 144 | } 145 | -------------------------------------------------------------------------------- /toml/README.md: -------------------------------------------------------------------------------- 1 | # toml 2 | 3 | Contains standard file wrapper methods for reading and writing TOML files: Open, Read, Save, Write. 4 | 5 | -------------------------------------------------------------------------------- /toml/toml.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package toml 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "io" 11 | "io/fs" 12 | "io/ioutil" 13 | "log" 14 | "os" 15 | 16 | "github.com/BurntSushi/toml" 17 | "github.com/goki/ki/dirs" 18 | ) 19 | 20 | // OpenFromPaths reads object from given TOML file, 21 | // looking on paths for the file. 22 | func OpenFromPaths(obj any, file string, paths []string) error { 23 | filename, err := dirs.FindFileOnPaths(paths, file) 24 | if err != nil { 25 | log.Println(err) 26 | return err 27 | } 28 | // _, err = toml.DecodeFile(fp, obj) 29 | fp, err := os.Open(filename) 30 | defer fp.Close() 31 | if err != nil { 32 | log.Println(err) 33 | return err 34 | } 35 | return Read(obj, bufio.NewReader(fp)) 36 | } 37 | 38 | // Open reads object from given TOML file. 39 | func Open(obj any, filename string) error { 40 | fp, err := os.Open(filename) 41 | defer fp.Close() 42 | if err != nil { 43 | log.Println(err) 44 | return err 45 | } 46 | return Read(obj, bufio.NewReader(fp)) 47 | } 48 | 49 | // OpenFS reads object from given TOML file, 50 | // using the fs.FS filesystem -- e.g., for embed files. 51 | func OpenFS(obj any, fsys fs.FS, file string) error { 52 | fp, err := fsys.Open(file) 53 | defer fp.Close() 54 | if err != nil { 55 | log.Println(err) 56 | return err 57 | } 58 | return Read(obj, bufio.NewReader(fp)) 59 | } 60 | 61 | // Read reads object TOML encoding from given reader, 62 | func Read(obj any, reader io.Reader) error { 63 | b, err := ioutil.ReadAll(reader) 64 | if err != nil { 65 | log.Println(err) 66 | return err 67 | } 68 | return ReadBytes(obj, b) 69 | } 70 | 71 | // ReadBytes reads TOML from given bytes, 72 | func ReadBytes(obj any, b []byte) error { 73 | err := toml.Unmarshal(b, obj) 74 | if err != nil { 75 | log.Println(err) 76 | return err 77 | } 78 | return nil 79 | } 80 | 81 | // Save writes TOML to given file. 82 | func Save(obj any, file string) error { 83 | fp, err := os.Create(file) 84 | defer fp.Close() 85 | if err != nil { 86 | log.Println(err) 87 | return err 88 | } 89 | bw := bufio.NewWriter(fp) 90 | err = Write(obj, bw) 91 | if err != nil { 92 | log.Println(err) 93 | return err 94 | } 95 | err = bw.Flush() 96 | if err != nil { 97 | log.Println(err) 98 | } 99 | return err 100 | } 101 | 102 | // Write writes TOML to given writer. 103 | func Write(obj any, writer io.Writer) error { 104 | enc := toml.NewEncoder(writer) 105 | return enc.Encode(obj) 106 | } 107 | 108 | // WriteBytes writes TOML returning bytes. 109 | func WriteBytes(obj any) []byte { 110 | var b bytes.Buffer 111 | enc := toml.NewEncoder(&b) 112 | enc.Encode(obj) 113 | return b.Bytes() 114 | } 115 | -------------------------------------------------------------------------------- /walki/walki.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package walki provides basic tree walking functions for iterative traversal 7 | of the tree in up / down directions. As compared to the core Func methods 8 | defined in ki package, these are for more dynamic, piecemeal processing. 9 | */ 10 | package walki 11 | 12 | import ( 13 | "github.com/goki/ki/ki" 14 | ) 15 | 16 | // Last returns the last node in the tree 17 | func Last(nd ki.Ki) ki.Ki { 18 | var last ki.Ki 19 | nd.FuncDownMeFirst(0, nd, func(k ki.Ki, level int, d any) bool { 20 | last = k 21 | return ki.Continue 22 | }) 23 | return last 24 | } 25 | 26 | // LastChild returns the last child under given node, or node itself if no children 27 | func LastChild(nd ki.Ki) ki.Ki { 28 | if nd.HasChildren() { 29 | ek, err := nd.Children().ElemFromEndTry(0) 30 | if err == nil { 31 | return LastChild(ek) 32 | } 33 | } 34 | return nd 35 | } 36 | 37 | // Prev returns previous node in the tree -- nil if top 38 | func Prev(nd ki.Ki) ki.Ki { 39 | if nd.Parent() == nil { 40 | return nil 41 | } 42 | myidx, ok := nd.IndexInParent() 43 | if ok && myidx > 0 { 44 | nn := nd.Parent().Child(myidx - 1) 45 | return LastChild(nn) 46 | } 47 | return nd.Parent() 48 | } 49 | 50 | // Next returns next node in the tree, nil if end 51 | func Next(nd ki.Ki) ki.Ki { 52 | if !nd.HasChildren() { 53 | return NextSibling(nd) 54 | } 55 | if nd.HasChildren() { 56 | return nd.Child(0) 57 | } 58 | return nil 59 | } 60 | 61 | // NextSibling returns next sibling or nil if none 62 | func NextSibling(nd ki.Ki) ki.Ki { 63 | if nd.Parent() == nil { 64 | return nil 65 | } 66 | myidx, ok := nd.IndexInParent() 67 | if ok { 68 | if myidx < nd.Parent().NumChildren()-1 { 69 | return nd.Parent().Child(myidx + 1) 70 | } 71 | } 72 | return NextSibling(nd.Parent()) 73 | } 74 | -------------------------------------------------------------------------------- /walki/walki_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, The GoKi Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package walki 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/goki/ki/ki" 13 | ) 14 | 15 | var testTree *ki.Node 16 | 17 | func init() { 18 | testTree = &ki.Node{} 19 | typ := reflect.TypeOf(testTree).Elem() 20 | testTree.InitName(testTree, "root") 21 | // child1 := 22 | testTree.AddNewChild(typ, "child0") 23 | var child2 = testTree.AddNewChild(typ, "child1") 24 | // child3 := 25 | testTree.AddNewChild(typ, "child2") 26 | schild2 := child2.AddNewChild(typ, "subchild1") 27 | // sschild2 := 28 | schild2.AddNewChild(typ, "subsubchild1") 29 | // child4 := 30 | testTree.AddNewChild(typ, "child3") 31 | } 32 | 33 | func TestDown(t *testing.T) { 34 | cur := testTree 35 | for { 36 | fmt.Println(cur.Path()) 37 | curi := Next(cur) 38 | if curi == nil { 39 | break 40 | } 41 | cur = curi.(*ki.Node) 42 | } 43 | } 44 | 45 | func TestUp(t *testing.T) { 46 | cur := Last(testTree) 47 | for { 48 | fmt.Println(cur.Path()) 49 | curi := Prev(cur) 50 | if curi == nil { 51 | break 52 | } 53 | cur = curi.(*ki.Node) 54 | } 55 | } 56 | --------------------------------------------------------------------------------