├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── builder ├── builder.go └── builder_test.go ├── component ├── component.go └── component_test.go ├── constraints ├── constraints.go └── constraints_test.go ├── container ├── heap │ ├── heap.go │ └── heap_test.go ├── history │ ├── doc.go │ ├── map.go │ ├── map_iter.go │ ├── map_test.go │ ├── recorder.go │ ├── recorder_test.go │ ├── set.go │ ├── set_iter.go │ ├── set_test.go │ ├── slice.go │ ├── slice_iter.go │ └── slice_test.go ├── iters │ ├── README.md │ ├── aggregator.go │ ├── aggregator_test.go │ ├── generator.go │ ├── generator_test.go │ ├── iters.go │ └── iters_test.go ├── pair │ ├── pair.go │ └── pair_test.go ├── tree │ ├── tree.go │ └── tree_test.go └── trie │ ├── trie.go │ └── trie_test.go ├── encoding ├── encoding.go └── encoding_test.go ├── enum ├── enum.go └── enum_test.go ├── errkit ├── doc.go ├── errno.go ├── errno_test.go └── errors.go ├── event ├── event.go └── event_test.go ├── flags ├── flags.go └── util.go ├── go.mod ├── lifecycle ├── lifecycle.go └── lifecycle_test.go ├── math ├── mathutil │ ├── mathutil.go │ └── mathutil_test.go └── random │ ├── random.go │ └── random_test.go ├── op ├── op.go └── op_test.go ├── service ├── config.go ├── config_test.go ├── service.go └── service_test.go ├── stringutil ├── stringutil.go └── stringutil_test.go ├── term ├── colors.go └── term.go ├── text ├── document │ ├── document.go │ └── document_test.go ├── templates │ ├── funcs.go │ ├── templates.go │ └── types.go └── text.go └── typing ├── typing.go └── typing_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | 11 | - name: Set up Go 12 | uses: actions/setup-go@v4 13 | with: 14 | go-version: "1.21" 15 | 16 | - name: Run tests with coverage 17 | run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... 18 | 19 | - name: Upload coverage to Codecov 20 | uses: codecov/codecov-action@v4 21 | if: github.event_name == 'push' 22 | with: 23 | token: ${{ secrets.CODECOV_TOKEN }} 24 | file: ./coverage.txt 25 | verbose: true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | ~* 4 | .DS_Store 5 | .vs/ 6 | .vscode/ 7 | .idea/ 8 | *.exe 9 | testdata/ 10 | 11 | # go coverage files 12 | coverage.out 13 | coverage.txt 14 | coverage.html 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 gopherd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 gopherd/core 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/gopherd/core.svg)](https://pkg.go.dev/github.com/gopherd/core) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/gopherd/core)](https://goreportcard.com/report/github.com/gopherd/core) 5 | [![codecov](https://codecov.io/gh/gopherd/core/branch/main/graph/badge.svg)](https://codecov.io/gh/gopherd/core) 6 | [![Build Status](https://github.com/gopherd/core/workflows/Go/badge.svg)](https://github.com/gopherd/core/actions) 7 | [![License](https://img.shields.io/github/license/gopherd/core.svg)](https://github.com/gopherd/core/blob/main/LICENSE) 8 | 9 | `gopherd/core` is a Go library that provides a component-based development framework for building backend services, leveraging the power of Go's generics. It's a modern, type-safe approach to creating scalable applications! 🌟 10 | 11 | ## 🌟 Overview 12 | 13 | This library offers a state-of-the-art mechanism for component-based development, enabling Go developers to create highly modular and maintainable backend services. By harnessing the power of `gopherd/core` and Go's generics, developers can: 14 | 15 | - 🧩 Easily create and manage type-safe components for various functionalities (e.g., database connections, caching, authentication) 16 | - 🔌 Implement a plugin-like architecture for extensible services with compile-time type checking 17 | - 🛠️ Utilize a set of fundamental helper functions to streamline common tasks, all with the benefits of generics 18 | 19 | The component-based approach, combined with Go's generics, allows for better organization, reusability, and scalability of your Go backend services. It's like LEGO for your code, but with perfect fit guaranteed by the type system! 🧱✨ 20 | 21 | ## 🔥 Key Features 22 | 23 | - **Modern, generic-based architecture**: Leverage Go's generics for type-safe component creation and management 24 | - **Flexible configuration**: Load configurations from files, URLs, or standard input with type safety 25 | - **Template processing**: Use Go templates in your component configurations for dynamic setups 26 | - **Multiple format support**: Handle JSON, TOML, YAML, and other arbitrary configuration formats through encoders and decoders 27 | - **Automatic dependency injection**: Simplify component integration with built-in dependency resolution and injection 28 | 29 | ## 📦 Installation 30 | 31 | To use `gopherd/core` in your Go project, install it using `go get`: 32 | 33 | ```bash 34 | go get github.com/gopherd/core 35 | ``` 36 | 37 | ## ⚡ Quick Start 38 | 39 | Here's a simple example showcasing the power of generics in our library: 40 | 41 | ```go 42 | // demo/main.go 43 | package main 44 | 45 | import ( 46 | "context" 47 | "fmt" 48 | 49 | "github.com/gopherd/core/component" 50 | "github.com/gopherd/core/service" 51 | ) 52 | 53 | // helloComponent demonstrates the use of generics for type-safe options. 54 | type helloComponent struct { 55 | component.BaseComponent[struct { 56 | Message string 57 | }] 58 | } 59 | 60 | func (c *helloComponent) Init(ctx context.Context) error { 61 | fmt.Println("Hello, " + c.Options().Message + "!") 62 | return nil 63 | } 64 | 65 | func init() { 66 | component.Register("hello", func() component.Component { return &helloComponent{} }) 67 | } 68 | 69 | func main() { 70 | service.Run() 71 | } 72 | ``` 73 | 74 | Yes, it's that simple and type-safe! 😮 With just these few lines of code, you can leverage the power of our generic-based, component-driven architecture. The simplicity of this example demonstrates how our library abstracts away the complexities of component management while maintaining type safety, allowing you to focus on building your application logic. Modern magic, right? ✨🔮 75 | 76 | ### Basic Usage 77 | 78 | Run your application with a configuration file: 79 | 80 | ```sh 81 | ./demo app.json 82 | ``` 83 | 84 | > Here's an example `app.json` 85 | 86 | ```json 87 | { 88 | "Components": [ 89 | { 90 | "Name": "hello", 91 | "Options": { 92 | "Message": "world" 93 | } 94 | } 95 | ] 96 | } 97 | ``` 98 | 99 | ### Load Configuration from Different Sources 100 | 101 | - From a file: `./demo app.json` 📄 102 | - From a URL: `./demo http://example.com/config/app.json` 🌐 103 | - From stdin: `echo '{"Components":[...]}' | ./demo -` ⌨️ 104 | 105 | ### Command-line Options 106 | 107 | - `-p`: Print the configuration 🖨️ 108 | - `-t`: Test the configuration for validity ✅ 109 | - `-T`: Enable template processing for component configurations 🧩 110 | 111 | ## 🎓 Example Project 112 | 113 | For a more comprehensive example of how to use `gopherd/core` in a real-world scenario, check out our example project: 114 | 115 | [https://github.com/gopherd/example](https://github.com/gopherd/example) 116 | 117 | This project demonstrates how to build a modular backend service using `gopherd/core`, including: 118 | 119 | - Setting up multiple components 120 | - Configuring dependencies between components 121 | - Using the event system 122 | - Implementing authentication and user management 123 | 124 | It's a great resource for understanding how all the pieces fit together in a larger application! 🧩 125 | 126 | ## 📚 Documentation 127 | 128 | For detailed documentation of each package and component, please refer to the GoDoc: 129 | 130 | [https://pkg.go.dev/github.com/gopherd/core](https://pkg.go.dev/github.com/gopherd/core) 131 | 132 | ## 👥 Contributing 133 | 134 | Contributions are welcome! Please feel free to submit a Pull Request. Let's make this library even more awesome together! 🤝 135 | 136 | ## 📜 License 137 | 138 | This project is licensed under the [MIT License](LICENSE). 139 | 140 | ## 🆘 Support 141 | 142 | If you encounter any problems or have any questions, please open an issue in this repository. We're here to help! 💪 143 | 144 | --- 145 | 146 | We hope you find `gopherd/core` valuable for your modern Go backend projects! Whether you're building a small microservice or a complex distributed system, `gopherd/core` provides the foundation for creating modular, maintainable, and efficient backend services with the power of generics. Welcome to the future of Go development! 🚀🎉 -------------------------------------------------------------------------------- /builder/builder.go: -------------------------------------------------------------------------------- 1 | // Package builder provides utilities for managing and displaying build information. 2 | // 3 | // It allows for compile-time injection of version details and offers methods 4 | // to retrieve and print this information. 5 | // 6 | // Usage: 7 | // 8 | // To use this package, import it in your main application: 9 | // 10 | // import "github.com/gopherd/core/builder" 11 | // 12 | // You can then use the provided functions to access build information: 13 | // 14 | // fmt.Println(builder.Info()) // Prints the version string 15 | // builder.PrintInfo() // Prints a string with all build details 16 | // 17 | // Compile-time configuration: 18 | // 19 | // To set the build information at compile time, use the -ldflags option with go build 20 | // or go install. Here's an example: 21 | // 22 | // go build -ldflags "\ 23 | // -X github.com/gopherd/core/builder.name=myapp \ 24 | // -X github.com/gopherd/core/builder.version=v1.0.0 \ 25 | // -X github.com/gopherd/core/builder.branch=main \ 26 | // -X github.com/gopherd/core/builder.commit=abc123 \ 27 | // -X github.com/gopherd/core/builder.datetime=2023-08-04T12:00:00Z" \ 28 | // ./cmd/myapp 29 | // 30 | // For convenience, you can use a Makefile to automate this process: 31 | // 32 | // BUILD_NAME := myapp 33 | // BUILD_VERSION := $(shell git describe --tags --always --dirty) 34 | // BUILD_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 35 | // BUILD_COMMIT := $(shell git rev-parse --short HEAD) 36 | // BUILD_DATETIME := $(shell date +"%Y-%m-%dT%H:%M:%S%z") 37 | // BUILD_PKG := github.com/gopherd/core/builder 38 | // 39 | // build: 40 | // go build -ldflags "\ 41 | // -X $(BUILD_PKG).name=$(BUILD_NAME) \ 42 | // -X $(BUILD_PKG).version=$(BUILD_VERSION) \ 43 | // -X $(BUILD_PKG).branch=$(BUILD_BRANCH) \ 44 | // -X $(BUILD_PKG).commit=$(BUILD_COMMIT) \ 45 | // -X $(BUILD_PKG).datetime=$(BUILD_DATETIME)" \ 46 | // ./cmd/myapp 47 | // 48 | // This setup allows for flexible and automated injection of build information 49 | // without modifying the source code. 50 | package builder 51 | 52 | import ( 53 | "fmt" 54 | "os" 55 | "path/filepath" 56 | "runtime" 57 | "strings" 58 | ) 59 | 60 | var ( 61 | name string // Application name, set at compile time 62 | version string // Application version, set at compile time 63 | branch string // Git branch from which the application was built 64 | commit string // Git commit hash of the built application 65 | datetime string // Build timestamp 66 | ) 67 | 68 | // appName returns the application name. If not set at compile time, 69 | // it derives the name from the executable filename. 70 | func appName() string { 71 | if name != "" { 72 | return name 73 | } 74 | exe, _ := os.Executable() 75 | return strings.TrimSuffix(filepath.Base(exe), ".exe") 76 | } 77 | 78 | // buildInfo contains all build information. 79 | type buildInfo struct { 80 | Name string 81 | Version string 82 | Branch string 83 | Commit string 84 | DateTime string 85 | } 86 | 87 | var runtimeVersion = runtime.Version 88 | 89 | // String returns a formatted string containing all build information. 90 | func (info buildInfo) String() string { 91 | br := info.Branch 92 | if br != "" { 93 | br += ": " 94 | } 95 | return fmt.Sprintf("%s %s(%s%s) built at %s by %s", 96 | info.Name, info.Version, br, info.Commit, info.DateTime, runtimeVersion()) 97 | } 98 | 99 | // Info returns a struct containing the build information. 100 | func Info() buildInfo { 101 | return buildInfo{ 102 | Name: appName(), 103 | Version: version, 104 | Branch: branch, 105 | Commit: commit, 106 | DateTime: datetime, 107 | } 108 | } 109 | 110 | // PrintInfo outputs the full build information to stdout. 111 | func PrintInfo() { 112 | fmt.Println(Info().String()) 113 | } 114 | -------------------------------------------------------------------------------- /builder/builder_test.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import "testing" 4 | 5 | func TestInfo_String(t *testing.T) { 6 | runtimeVersion = func() string { 7 | return "go.test" 8 | } 9 | tests := []struct { 10 | name string 11 | info buildInfo 12 | want string 13 | }{ 14 | { 15 | name: "empty", 16 | info: buildInfo{}, 17 | want: " () built at by go.test", 18 | }, 19 | { 20 | name: "full", 21 | info: buildInfo{ 22 | Name: "app", 23 | Version: "v1.0.0", 24 | Branch: "main", 25 | Commit: "abcdefg", 26 | DateTime: "2021-01-01T00:00:00Z", 27 | }, 28 | want: "app v1.0.0(main: abcdefg) built at 2021-01-01T00:00:00Z by go.test", 29 | }, 30 | } 31 | for _, tt := range tests { 32 | t.Run(tt.name, func(t *testing.T) { 33 | if got := tt.info.String(); got != tt.want { 34 | t.Errorf("buildInfo.String() = %q, want %q", got, tt.want) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestInfo(t *testing.T) { 41 | defaultAppName := appName() 42 | tests := []struct { 43 | name string 44 | want buildInfo 45 | }{ 46 | { 47 | name: "empty", 48 | want: buildInfo{ 49 | Name: "", 50 | Version: "", 51 | Branch: "", 52 | Commit: "", 53 | DateTime: "", 54 | }, 55 | }, 56 | { 57 | name: "full", 58 | want: buildInfo{ 59 | Name: "app", 60 | Version: "v1.0.0", 61 | Branch: "main", 62 | Commit: "abcdefg", 63 | DateTime: "2021-01-01T00:00:00Z", 64 | }, 65 | }, 66 | } 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | name = tt.want.Name 70 | version = tt.want.Version 71 | branch = tt.want.Branch 72 | commit = tt.want.Commit 73 | datetime = tt.want.DateTime 74 | if name == "" { 75 | tt.want.Name = defaultAppName 76 | } 77 | if got := Info(); got != tt.want { 78 | t.Errorf("Info() = %v, want %v", got, tt.want) 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /constraints/constraints.go: -------------------------------------------------------------------------------- 1 | // Package constraints provides type constraints for generic programming in Go. 2 | // 3 | // It defines a set of interface types that can be used as type constraints 4 | // in generic functions and types. These constraints cover various numeric 5 | // types and their combinations, allowing for more precise and flexible 6 | // generic programming with numbers in Go. 7 | package constraints 8 | 9 | // Signed is a constraint that permits any signed integer type. 10 | type Signed interface { 11 | ~int | ~int8 | ~int16 | ~int32 | ~int64 12 | } 13 | 14 | // Unsigned is a constraint that permits any unsigned integer type. 15 | type Unsigned interface { 16 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr 17 | } 18 | 19 | // Integer is a constraint that permits any integer type. 20 | type Integer interface { 21 | Signed | Unsigned 22 | } 23 | 24 | // Float is a constraint that permits any floating-point type. 25 | type Float interface { 26 | ~float32 | ~float64 27 | } 28 | 29 | // Complex is a constraint that permits any complex numeric type. 30 | type Complex interface { 31 | ~complex64 | ~complex128 32 | } 33 | 34 | // SignedReal is a constraint that permits any signed real number type. 35 | type SignedReal interface { 36 | Signed | Float 37 | } 38 | 39 | // Real is a constraint that permits any real number type. 40 | type Real interface { 41 | Integer | Float 42 | } 43 | 44 | // SignedNumber is a constraint that permits any signed numeric type. 45 | type SignedNumber interface { 46 | SignedReal | Complex 47 | } 48 | 49 | // Number is a constraint that permits any numeric type. 50 | type Number interface { 51 | Real | Complex 52 | } 53 | 54 | // Field is a constraint that permits any number field type. 55 | type Field interface { 56 | Float | Complex 57 | } 58 | 59 | // Addable is a constraint that permits any number or string type. 60 | type Addable interface { 61 | Number | string 62 | } 63 | -------------------------------------------------------------------------------- /constraints/constraints_test.go: -------------------------------------------------------------------------------- 1 | package constraints_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gopherd/core/constraints" 7 | ) 8 | 9 | func sum[T constraints.Addable](values ...T) T { 10 | var result T 11 | for _, v := range values { 12 | result += v 13 | } 14 | return result 15 | } 16 | 17 | func ExampleAddable() { 18 | fmt.Println(sum(1, 2)) 19 | fmt.Println(sum("hello", " ", "world")) 20 | // Output: 21 | // 3 22 | // hello world 23 | } 24 | -------------------------------------------------------------------------------- /container/heap/heap.go: -------------------------------------------------------------------------------- 1 | // Package heap provides a generic heap implementation. 2 | package heap 3 | 4 | import "sort" 5 | 6 | // The Interface type describes the requirements 7 | // for a type using the routines in this package. 8 | // Any type that implements it may be used as a 9 | // min-heap with the following invariants (established after 10 | // Init has been called or if the data is empty or sorted): 11 | // 12 | // !h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len() 13 | // 14 | // Note that Push and Pop in this interface are for package heap's 15 | // implementation to call. To add and remove things from the heap, 16 | // use heap.Push and heap.Pop. 17 | type Interface[T any] interface { 18 | sort.Interface 19 | Push(x T) // add x as element Len() 20 | Pop() T // remove and return element Len() - 1. 21 | } 22 | 23 | // Init establishes the heap invariants required by the other routines in this package. 24 | // Init is idempotent with respect to the heap invariants 25 | // and may be called whenever the heap invariants may have been invalidated. 26 | // The complexity is O(n) where n = h.Len(). 27 | func Init[T any](h Interface[T]) { 28 | // heapify 29 | n := h.Len() 30 | for i := n/2 - 1; i >= 0; i-- { 31 | down(h, i, n) 32 | } 33 | } 34 | 35 | // Push pushes the element x onto the heap. 36 | // The complexity is O(log n) where n = h.Len(). 37 | func Push[T any](h Interface[T], x T) { 38 | h.Push(x) 39 | up(h, h.Len()-1) 40 | } 41 | 42 | // Pop removes and returns the minimum element (according to Less) from the heap. 43 | // The complexity is O(log n) where n = h.Len(). 44 | // Pop is equivalent to Remove(h, 0). 45 | func Pop[T any](h Interface[T]) T { 46 | n := h.Len() - 1 47 | h.Swap(0, n) 48 | down(h, 0, n) 49 | return h.Pop() 50 | } 51 | 52 | // Remove removes and returns the element at index i from the heap. 53 | // The complexity is O(log n) where n = h.Len(). 54 | func Remove[T any](h Interface[T], i int) T { 55 | n := h.Len() - 1 56 | if n != i { 57 | h.Swap(i, n) 58 | if !down(h, i, n) { 59 | up(h, i) 60 | } 61 | } 62 | return h.Pop() 63 | } 64 | 65 | // Fix re-establishes the heap ordering after the element at index i has changed its value. 66 | // Changing the value of the element at index i and then calling Fix is equivalent to, 67 | // but less expensive than, calling Remove(h, i) followed by a Push of the new value. 68 | // The complexity is O(log n) where n = h.Len(). 69 | func Fix[T any](h Interface[T], i int) { 70 | if !down(h, i, h.Len()) { 71 | up(h, i) 72 | } 73 | } 74 | 75 | func up[T any](h Interface[T], j int) { 76 | for { 77 | i := (j - 1) / 2 // parent 78 | if i == j || !h.Less(j, i) { 79 | break 80 | } 81 | h.Swap(i, j) 82 | j = i 83 | } 84 | } 85 | 86 | func down[T any](h Interface[T], i0, n int) bool { 87 | i := i0 88 | for { 89 | j1 := 2*i + 1 90 | if j1 >= n || j1 < 0 { // j1 < 0 after int overflow 91 | break 92 | } 93 | j := j1 // left child 94 | if j2 := j1 + 1; j2 < n && h.Less(j2, j1) { 95 | j = j2 // = 2*i + 2 // right child 96 | } 97 | if !h.Less(j, i) { 98 | break 99 | } 100 | h.Swap(i, j) 101 | i = j 102 | } 103 | return i > i0 104 | } 105 | -------------------------------------------------------------------------------- /container/heap/heap_test.go: -------------------------------------------------------------------------------- 1 | package heap_test 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/gopherd/core/container/heap" 9 | ) 10 | 11 | // intHeap is a min-heap of integers. 12 | type intHeap []int 13 | 14 | func (h intHeap) Len() int { return len(h) } 15 | func (h intHeap) Less(i, j int) bool { return h[i] < h[j] } 16 | func (h intHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 17 | func (h *intHeap) Push(x int) { *h = append(*h, x) } 18 | func (h *intHeap) Pop() int { 19 | old := *h 20 | n := len(old) 21 | x := old[n-1] 22 | *h = old[0 : n-1] 23 | return x 24 | } 25 | 26 | func TestHeapInit(t *testing.T) { 27 | h := &intHeap{3, 2, 1, 5, 6, 4} 28 | heap.Init(h) 29 | 30 | for i := 1; i < h.Len(); i++ { 31 | if h.Less(i, (i-1)/2) { 32 | t.Errorf("heap invariant violated: h[%d] = %d < h[%d] = %d", i, (*h)[i], (i-1)/2, (*h)[(i-1)/2]) 33 | } 34 | } 35 | } 36 | 37 | func TestHeapPush(t *testing.T) { 38 | h := &intHeap{2, 1, 5} 39 | heap.Init(h) 40 | heap.Push(h, 3) 41 | 42 | if (*h)[0] != 1 { 43 | t.Errorf("expected minimum element 1, got %d", (*h)[0]) 44 | } 45 | 46 | if h.Len() != 4 { 47 | t.Errorf("expected length 4, got %d", h.Len()) 48 | } 49 | } 50 | 51 | func TestHeapPop(t *testing.T) { 52 | h := &intHeap{1, 2, 3, 4, 5} 53 | heap.Init(h) 54 | 55 | for i := 1; i <= 5; i++ { 56 | x := heap.Pop(h) 57 | if x != i { 58 | t.Errorf("pop got %d, want %d", x, i) 59 | } 60 | } 61 | 62 | if h.Len() != 0 { 63 | t.Errorf("expected heap to be empty, got length %d", h.Len()) 64 | } 65 | } 66 | 67 | func TestHeapRemove(t *testing.T) { 68 | h := &intHeap{1, 2, 3, 4, 5} 69 | heap.Init(h) 70 | 71 | x := heap.Remove(h, 2) 72 | if x != 3 { 73 | t.Errorf("Remove(2) got %d, want 3", x) 74 | } 75 | 76 | if h.Len() != 4 { 77 | t.Errorf("expected length 4, got %d", h.Len()) 78 | } 79 | 80 | // Verify heap invariant 81 | if !verifyHeap(h) { 82 | t.Errorf("heap invariant violated after Remove") 83 | } 84 | 85 | // Verify all original elements except the removed one are still in the heap 86 | remaining := []int{1, 2, 4, 5} 87 | for _, v := range remaining { 88 | found := false 89 | for _, hv := range *h { 90 | if hv == v { 91 | found = true 92 | break 93 | } 94 | } 95 | if !found { 96 | t.Errorf("expected to find %d in heap after Remove, but it's missing", v) 97 | } 98 | } 99 | } 100 | 101 | // verifyHeap checks if the heap invariant is maintained 102 | func verifyHeap(h *intHeap) bool { 103 | for i := 1; i < h.Len(); i++ { 104 | if h.Less(i, (i-1)/2) { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | 111 | func TestHeapFix(t *testing.T) { 112 | h := &intHeap{1, 2, 3, 4, 5} 113 | heap.Init(h) 114 | 115 | (*h)[0] = 6 116 | heap.Fix(h, 0) 117 | 118 | expected := []int{2, 4, 3, 6, 5} 119 | for i, v := range *h { 120 | if v != expected[i] { 121 | t.Errorf("at index %d, got %d, want %d", i, v, expected[i]) 122 | } 123 | } 124 | } 125 | 126 | func TestHeapFixUp(t *testing.T) { 127 | h := &intHeap{1, 3, 2, 4, 5} 128 | heap.Init(h) 129 | 130 | // Change a leaf node to a smaller value 131 | (*h)[4] = 0 132 | heap.Fix(h, 4) 133 | 134 | if !verifyHeap(h) { 135 | t.Errorf("heap invariant violated after Fix (up)") 136 | } 137 | 138 | // The smallest value should now be at the root 139 | if (*h)[0] != 0 { 140 | t.Errorf("expected root to be 0, got %d", (*h)[0]) 141 | } 142 | 143 | // Verify the heap structure 144 | expected := []int{0, 1, 2, 4, 3} 145 | for i, v := range *h { 146 | if v != expected[i] { 147 | t.Errorf("at index %d, got %d, want %d", i, v, expected[i]) 148 | } 149 | } 150 | } 151 | 152 | func TestHeapIntegration(t *testing.T) { 153 | h := &intHeap{} 154 | 155 | // Push elements 156 | for i := 20; i > 0; i-- { 157 | heap.Push(h, i) 158 | } 159 | 160 | // Verify heap property 161 | for i := 1; i < h.Len(); i++ { 162 | if h.Less(i, (i-1)/2) { 163 | t.Errorf("heap invariant violated: h[%d] = %d < h[%d] = %d", i, (*h)[i], (i-1)/2, (*h)[(i-1)/2]) 164 | } 165 | } 166 | 167 | // Pop all elements 168 | for i := 1; h.Len() > 0; i++ { 169 | x := heap.Pop(h) 170 | if x != i { 171 | t.Errorf("pop got %d, want %d", x, i) 172 | } 173 | } 174 | } 175 | 176 | func TestHeapWithRandomData(t *testing.T) { 177 | h := &intHeap{} 178 | data := rand.Perm(1000) 179 | 180 | for _, v := range data { 181 | heap.Push(h, v) 182 | } 183 | 184 | sort.Ints(data) 185 | 186 | for i, want := range data { 187 | got := heap.Pop(h) 188 | if got != want { 189 | t.Errorf("pop %d got %d, want %d", i, got, want) 190 | } 191 | } 192 | } 193 | 194 | func BenchmarkHeapPush(b *testing.B) { 195 | h := &intHeap{} 196 | for i := 0; i < b.N; i++ { 197 | heap.Push(h, i) 198 | } 199 | } 200 | 201 | func BenchmarkHeapPop(b *testing.B) { 202 | h := &intHeap{} 203 | for i := 0; i < b.N; i++ { 204 | heap.Push(h, i) 205 | } 206 | b.ResetTimer() 207 | for i := 0; i < b.N; i++ { 208 | heap.Pop(h) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /container/history/doc.go: -------------------------------------------------------------------------------- 1 | // Package history provides data structures with undo functionality. 2 | // 3 | // This package includes implementations of Map, Set, and Slice data structures 4 | // that support undo operations. It also provides a Recorder interface for 5 | // managing undo actions. 6 | // 7 | // The main components of this package are: 8 | // 9 | // - Map: A generic map that supports undo operations. 10 | // - Set: A generic set that supports undo operations. 11 | // - Slice: A generic slice that supports undo operations. 12 | // - Recorder: An interface for managing undo actions. 13 | // - BaseRecorder: A basic implementation of the Recorder interface. 14 | // 15 | // Each data structure (Map, Set, and Slice) is designed to work with a Recorder, 16 | // which keeps track of changes and allows for undoing operations. The BaseRecorder 17 | // provides a simple implementation of the Recorder interface that can be used 18 | // with any of the data structures. 19 | // 20 | // Example usage: 21 | // 22 | // recorder := &history.BaseRecorder{} 23 | // myMap := history.NewMap[string, int](recorder, 10) 24 | // myMap.Set("key", 42) 25 | // value, _ := myMap.Get("key") 26 | // fmt.Println(value) // Output: 42 27 | // recorder.Undo() 28 | // _, ok := myMap.Get("key") 29 | // fmt.Println(ok) // Output: false 30 | // 31 | // This package is useful for scenarios where you need to maintain a history of 32 | // changes and potentially revert them, such as in text editors, game state 33 | // management, or any application where an undo feature is desired. 34 | package history 35 | -------------------------------------------------------------------------------- /container/history/map.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "strings" 7 | ) 8 | 9 | // Map is a generic map with keys of type K and values of type V that supports undo operations. 10 | type Map[K comparable, V any] struct { 11 | recorder Recorder 12 | data map[K]V 13 | } 14 | 15 | // NewMap creates a new Map with the given recorder and initial size. 16 | func NewMap[K comparable, V any](recorder Recorder, size int) *Map[K, V] { 17 | return &Map[K, V]{ 18 | recorder: recorder, 19 | data: make(map[K]V, size), 20 | } 21 | } 22 | 23 | // String returns a string representation of the Map. 24 | func (m Map[K, V]) String() string { 25 | var sb strings.Builder 26 | sb.Grow(len(m.data)*18 + 1) 27 | sb.WriteByte('{') 28 | i := 0 29 | for k, v := range m.data { 30 | if i > 0 { 31 | sb.WriteByte(',') 32 | } 33 | fmt.Fprintf(&sb, "%v:%v", k, v) 34 | i++ 35 | } 36 | sb.WriteByte('}') 37 | return sb.String() 38 | } 39 | 40 | // Clone creates a deep copy of the Map with a new recorder. 41 | func (m *Map[K, V]) Clone(recorder Recorder) *Map[K, V] { 42 | return &Map[K, V]{ 43 | recorder: recorder, 44 | data: maps.Clone(m.data), 45 | } 46 | } 47 | 48 | // Len returns the number of elements in the Map. 49 | func (m *Map[K, V]) Len() int { 50 | return len(m.data) 51 | } 52 | 53 | // Contains checks if the Map contains the given key. 54 | func (m *Map[K, V]) Contains(k K) bool { 55 | _, ok := m.data[k] 56 | return ok 57 | } 58 | 59 | // Get retrieves the value for a key in the Map. 60 | func (m *Map[K, V]) Get(k K) (v V, ok bool) { 61 | v, ok = m.data[k] 62 | return 63 | } 64 | 65 | // Set adds or updates a key-value pair in the Map. 66 | // It returns true if an existing entry was updated. 67 | func (m *Map[K, V]) Set(k K, v V) bool { 68 | old, replaced := m.data[k] 69 | if replaced { 70 | m.recorder.PushAction(&mapUndoSetAction[K, V]{m: m, k: k, v: old, replaced: true}) 71 | } else { 72 | m.recorder.PushAction(&mapUndoSetAction[K, V]{m: m, k: k}) 73 | } 74 | m.data[k] = v 75 | return replaced 76 | } 77 | 78 | type mapUndoSetAction[K comparable, V any] struct { 79 | m *Map[K, V] 80 | k K 81 | v V 82 | replaced bool 83 | } 84 | 85 | func (r *mapUndoSetAction[K, V]) Undo() { 86 | if r.replaced { 87 | r.m.data[r.k] = r.v 88 | } else { 89 | delete(r.m.data, r.k) 90 | } 91 | } 92 | 93 | // Remove removes a key-value pair from the Map. 94 | // It returns the removed value and a boolean indicating if the key was present. 95 | func (m *Map[K, V]) Remove(k K) (v V, removed bool) { 96 | v, removed = m.data[k] 97 | if removed { 98 | m.recorder.PushAction(&mapUndoRemoveAction[K, V]{m: m, k: k, v: v}) 99 | } 100 | delete(m.data, k) 101 | return 102 | } 103 | 104 | type mapUndoRemoveAction[K comparable, V any] struct { 105 | m *Map[K, V] 106 | k K 107 | v V 108 | } 109 | 110 | func (r *mapUndoRemoveAction[K, V]) Undo() { 111 | r.m.data[r.k] = r.v 112 | } 113 | 114 | // Range calls f sequentially for each key and value in the Map. 115 | // If f returns false, Range stops the iteration. 116 | func (m *Map[K, V]) Range(f func(K, V) bool) bool { 117 | for k, v := range m.data { 118 | if !f(k, v) { 119 | return false 120 | } 121 | } 122 | return true 123 | } 124 | 125 | // Clear removes all elements from the Map. 126 | func (m *Map[K, V]) Clear() { 127 | m.recorder.PushAction(&mapUndoClearAction[K, V]{m: m, values: maps.Clone(m.data)}) 128 | clear(m.data) 129 | } 130 | 131 | type mapUndoClearAction[K comparable, V any] struct { 132 | m *Map[K, V] 133 | values map[K]V 134 | } 135 | 136 | func (r *mapUndoClearAction[K, V]) Undo() { 137 | r.m.data = r.values 138 | } 139 | -------------------------------------------------------------------------------- /container/history/map_iter.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package history 4 | 5 | import ( 6 | "iter" 7 | "maps" 8 | ) 9 | 10 | // All returns an iterator over key-value pairs in the map 11 | func (m Map[K, V]) All() iter.Seq2[K, V] { 12 | return maps.All(m.data) 13 | } 14 | 15 | // Keys returns an iterator over the map keys 16 | func (m Map[K, V]) Keys() iter.Seq[K] { 17 | return maps.Keys(m.data) 18 | } 19 | 20 | // Values returns an iterator over the map values 21 | func (m Map[K, V]) Values() iter.Seq[V] { 22 | return maps.Values(m.data) 23 | } 24 | -------------------------------------------------------------------------------- /container/history/map_test.go: -------------------------------------------------------------------------------- 1 | package history_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/gopherd/core/container/history" 10 | ) 11 | 12 | func TestMap_NewMap(t *testing.T) { 13 | recorder := &history.BaseRecorder{} 14 | m := history.NewMap[string, int](recorder, 10) 15 | 16 | if m == nil { 17 | t.Fatal("NewMap returned nil") 18 | } 19 | 20 | if m.Len() != 0 { 21 | t.Errorf("Expected length 0, got %d", m.Len()) 22 | } 23 | } 24 | 25 | func TestMap_String(t *testing.T) { 26 | recorder := &history.BaseRecorder{} 27 | m := history.NewMap[string, int](recorder, 3) 28 | 29 | m.Set("a", 1) 30 | m.Set("b", 2) 31 | m.Set("c", 3) 32 | 33 | s := m.String() 34 | if len(s) == 0 || s[0] != '{' || s[len(s)-1] != '}' { 35 | t.Errorf("Invalid string representation: %s", s) 36 | } 37 | if !containsPair(s, "a", 1) || !containsPair(s, "b", 2) || !containsPair(s, "c", 3) { 38 | t.Errorf("String representation missing expected pairs: %s", s) 39 | } 40 | } 41 | 42 | func containsPair(s string, key string, value int) bool { 43 | return strings.Contains(s, fmt.Sprintf("%s:%d", key, value)) 44 | } 45 | 46 | func TestMap_Clone(t *testing.T) { 47 | recorder := &history.BaseRecorder{} 48 | m := history.NewMap[string, int](recorder, 3) 49 | 50 | m.Set("a", 1) 51 | m.Set("b", 2) 52 | 53 | newRecorder := &history.BaseRecorder{} 54 | clone := m.Clone(newRecorder) 55 | 56 | if clone.Len() != m.Len() { 57 | t.Errorf("Expected clone length %d, got %d", m.Len(), clone.Len()) 58 | } 59 | 60 | m.Set("c", 3) 61 | if clone.Len() == m.Len() { 62 | t.Error("Clone should not be affected by changes to original map") 63 | } 64 | 65 | if v, ok := clone.Get("a"); !ok || v != 1 { 66 | t.Errorf("Expected clone to have key 'a' with value 1, got %v, %v", v, ok) 67 | } 68 | } 69 | 70 | func TestMap_Len(t *testing.T) { 71 | recorder := &history.BaseRecorder{} 72 | m := history.NewMap[string, int](recorder, 5) 73 | 74 | if m.Len() != 0 { 75 | t.Errorf("Expected length 0, got %d", m.Len()) 76 | } 77 | 78 | m.Set("a", 1) 79 | m.Set("b", 2) 80 | 81 | if m.Len() != 2 { 82 | t.Errorf("Expected length 2, got %d", m.Len()) 83 | } 84 | 85 | m.Remove("a") 86 | 87 | if m.Len() != 1 { 88 | t.Errorf("Expected length 1, got %d", m.Len()) 89 | } 90 | } 91 | 92 | func TestMap_Contains(t *testing.T) { 93 | recorder := &history.BaseRecorder{} 94 | m := history.NewMap[string, int](recorder, 3) 95 | 96 | m.Set("a", 1) 97 | 98 | if !m.Contains("a") { 99 | t.Error("Expected map to contain 'a'") 100 | } 101 | 102 | if m.Contains("b") { 103 | t.Error("Expected map to not contain 'b'") 104 | } 105 | } 106 | 107 | func TestMap_Get(t *testing.T) { 108 | recorder := &history.BaseRecorder{} 109 | m := history.NewMap[string, int](recorder, 3) 110 | 111 | m.Set("a", 1) 112 | 113 | if v, ok := m.Get("a"); !ok || v != 1 { 114 | t.Errorf("Expected (1, true), got (%v, %v)", v, ok) 115 | } 116 | 117 | if v, ok := m.Get("b"); ok || v != 0 { 118 | t.Errorf("Expected (0, false), got (%v, %v)", v, ok) 119 | } 120 | } 121 | 122 | func TestMap_Set(t *testing.T) { 123 | recorder := &history.BaseRecorder{} 124 | m := history.NewMap[string, int](recorder, 3) 125 | 126 | replaced := m.Set("a", 1) 127 | if replaced { 128 | t.Error("Expected Set to return false for new key") 129 | } 130 | 131 | replaced = m.Set("a", 2) 132 | if !replaced { 133 | t.Error("Expected Set to return true for existing key") 134 | } 135 | 136 | if v, ok := m.Get("a"); !ok || v != 2 { 137 | t.Errorf("Expected (2, true), got (%v, %v)", v, ok) 138 | } 139 | } 140 | 141 | func TestMap_Remove(t *testing.T) { 142 | recorder := &history.BaseRecorder{} 143 | m := history.NewMap[string, int](recorder, 3) 144 | 145 | m.Set("a", 1) 146 | 147 | v, removed := m.Remove("a") 148 | if !removed || v != 1 { 149 | t.Errorf("Expected (1, true), got (%v, %v)", v, removed) 150 | } 151 | 152 | v, removed = m.Remove("b") 153 | if removed || v != 0 { 154 | t.Errorf("Expected (0, false), got (%v, %v)", v, removed) 155 | } 156 | } 157 | 158 | func TestMap_Range(t *testing.T) { 159 | recorder := &history.BaseRecorder{} 160 | m := history.NewMap[string, int](recorder, 3) 161 | 162 | m.Set("a", 1) 163 | m.Set("b", 2) 164 | m.Set("c", 3) 165 | 166 | expectedPairs := map[string]int{"a": 1, "b": 2, "c": 3} 167 | visitedPairs := make(map[string]int) 168 | 169 | m.Range(func(k string, v int) bool { 170 | visitedPairs[k] = v 171 | return true 172 | }) 173 | 174 | if !reflect.DeepEqual(expectedPairs, visitedPairs) { 175 | t.Errorf("Range did not visit all expected pairs. Expected %v, got %v", expectedPairs, visitedPairs) 176 | } 177 | 178 | count := 0 179 | m.Range(func(k string, v int) bool { 180 | count++ 181 | return count < 2 182 | }) 183 | 184 | if count != 2 { 185 | t.Errorf("Range did not stop after returning false. Expected 2 iterations, got %d", count) 186 | } 187 | } 188 | 189 | func TestMap_Clear(t *testing.T) { 190 | recorder := &history.BaseRecorder{} 191 | m := history.NewMap[string, int](recorder, 3) 192 | 193 | m.Set("a", 1) 194 | m.Set("b", 2) 195 | 196 | m.Clear() 197 | 198 | if m.Len() != 0 { 199 | t.Errorf("Expected length 0 after Clear, got %d", m.Len()) 200 | } 201 | 202 | if m.Contains("a") || m.Contains("b") { 203 | t.Error("Map should not contain any elements after Clear") 204 | } 205 | } 206 | 207 | func TestMap_Undo(t *testing.T) { 208 | recorder := &history.BaseRecorder{} 209 | m := history.NewMap[string, int](recorder, 5) 210 | 211 | m.Set("a", 1) 212 | m.Set("b", 2) 213 | m.Set("a", 3) 214 | m.Remove("b") 215 | m.Clear() 216 | 217 | recorder.Undo() // Undo Clear 218 | if m.Len() != 1 || !m.Contains("a") { 219 | t.Errorf("After Undo Clear, expected map with 'a', got %v", m) 220 | } 221 | 222 | recorder.Undo() // Undo Remove("b") 223 | if m.Len() != 2 || !m.Contains("a") || !m.Contains("b") { 224 | t.Errorf("After Undo Remove, expected map with 'a' and 'b', got %v", m) 225 | } 226 | 227 | recorder.Undo() // Undo Set("a", 3) 228 | if v, _ := m.Get("a"); v != 1 { 229 | t.Errorf("After Undo Set, expected 'a' to be 1, got %v", v) 230 | } 231 | 232 | recorder.Undo() // Undo Set("b", 2) 233 | if m.Contains("b") { 234 | t.Errorf("After Undo Set, 'b' should not be in the map") 235 | } 236 | 237 | recorder.Undo() // Undo Set("a", 1) 238 | if m.Len() != 0 { 239 | t.Errorf("After final Undo, expected empty map, got %v", m) 240 | } 241 | } 242 | 243 | func TestMap_UndoEmptyMap(t *testing.T) { 244 | recorder := &history.BaseRecorder{} 245 | m := history.NewMap[string, int](recorder, 5) 246 | 247 | // Undo on an empty map should not panic 248 | recorder.Undo() 249 | 250 | if m.Len() != 0 { 251 | t.Errorf("Expected empty map after Undo on empty map, got %v", m) 252 | } 253 | } 254 | 255 | func TestMap_SetThenUndo(t *testing.T) { 256 | recorder := &history.BaseRecorder{} 257 | m := history.NewMap[string, int](recorder, 5) 258 | 259 | m.Set("a", 1) 260 | recorder.Undo() 261 | 262 | if m.Contains("a") { 263 | t.Errorf("After Undo Set, 'a' should not be in the map") 264 | } 265 | } 266 | 267 | func TestMap_RemoveThenUndo(t *testing.T) { 268 | recorder := &history.BaseRecorder{} 269 | m := history.NewMap[string, int](recorder, 5) 270 | 271 | m.Set("a", 1) 272 | m.Remove("a") 273 | recorder.Undo() 274 | 275 | if v, ok := m.Get("a"); !ok || v != 1 { 276 | t.Errorf("After Undo Remove, expected 'a' to be 1, got %v, %v", v, ok) 277 | } 278 | } 279 | 280 | func TestMap_ClearThenUndo(t *testing.T) { 281 | recorder := &history.BaseRecorder{} 282 | m := history.NewMap[string, int](recorder, 5) 283 | 284 | m.Set("a", 1) 285 | m.Set("b", 2) 286 | m.Clear() 287 | recorder.Undo() 288 | 289 | if m.Len() != 2 || !m.Contains("a") || !m.Contains("b") { 290 | t.Errorf("After Undo Clear, expected map with 'a' and 'b', got %v", m) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /container/history/recorder.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | // Recorder defines the interface for adding undo actions and performing undo operations. 4 | type Recorder interface { 5 | // PushAction adds a new undo action to the recorder. 6 | PushAction(a Action) 7 | 8 | // UndoAll reverts all actions in reverse order and clears the recorder. 9 | // It returns the number of actions undone. 10 | UndoAll() (n int) 11 | 12 | // Undo reverts the last action in the recorder. 13 | // It returns true if an action was undone. 14 | Undo() bool 15 | } 16 | 17 | // Action represents a single undoable action. 18 | type Action interface { 19 | // Undo reverts the changes made by this action. 20 | Undo() 21 | } 22 | 23 | // BaseRecorder implements the Recorder interface using a slice of Actions. 24 | type BaseRecorder struct { 25 | actions []Action 26 | } 27 | 28 | // Len returns the number of actions in the recorder. 29 | func (r *BaseRecorder) Len() int { 30 | return len(r.actions) 31 | } 32 | 33 | // PushAction appends a new Action to the BaseRecorder. 34 | func (r *BaseRecorder) PushAction(a Action) { 35 | r.actions = append(r.actions, a) 36 | } 37 | 38 | // UndoAll reverts all actions in reverse order and clears the recorder. 39 | // It returns the number of actions undone. 40 | func (r *BaseRecorder) UndoAll() (n int) { 41 | n = len(r.actions) 42 | for i := len(r.actions) - 1; i >= 0; i-- { 43 | r.actions[i].Undo() 44 | r.actions[i] = nil // Allow garbage collection 45 | } 46 | r.actions = r.actions[:0] 47 | return 48 | } 49 | 50 | // Undo reverts the last action in the recorder. 51 | // It returns true if an action was undone. 52 | func (r *BaseRecorder) Undo() bool { 53 | if len(r.actions) == 0 { 54 | return false 55 | } 56 | i := len(r.actions) - 1 57 | r.actions[i].Undo() 58 | r.actions[i] = nil // Allow garbage collection 59 | r.actions = r.actions[:i] 60 | return true 61 | } 62 | 63 | // ValueUndoAction creates a new Action for undoing changes to a value of any type. 64 | func ValueUndoAction[T any](ptr *T, old T) Action { 65 | return &valueUndoAction[T]{ 66 | ptr: ptr, 67 | old: old, 68 | } 69 | } 70 | 71 | type valueUndoAction[T any] struct { 72 | ptr *T 73 | old T 74 | } 75 | 76 | // Undo restores the original value. 77 | func (a *valueUndoAction[T]) Undo() { 78 | *a.ptr = a.old 79 | } 80 | -------------------------------------------------------------------------------- /container/history/recorder_test.go: -------------------------------------------------------------------------------- 1 | package history_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopherd/core/container/history" 7 | ) 8 | 9 | func TestDefaultRecorder_AddRecord(t *testing.T) { 10 | recorder := &history.BaseRecorder{} 11 | 12 | t.Run("Add single record", func(t *testing.T) { 13 | value := 5 14 | action := history.ValueUndoAction(&value, 0) 15 | recorder.PushAction(action) 16 | }) 17 | 18 | t.Run("Add multiple records", func(t *testing.T) { 19 | value1, value2 := 10, "test" 20 | action1 := history.ValueUndoAction(&value1, 0) 21 | action2 := history.ValueUndoAction(&value2, "") 22 | 23 | recorder.PushAction(action1) 24 | recorder.PushAction(action2) 25 | }) 26 | } 27 | 28 | func TestDefaultRecorder_Undo(t *testing.T) { 29 | t.Run("Undo single record", func(t *testing.T) { 30 | recorder := &history.BaseRecorder{} 31 | value := 5 32 | originalValue := 0 33 | action := history.ValueUndoAction(&value, originalValue) 34 | recorder.PushAction(action) 35 | 36 | recorder.Undo() 37 | 38 | if value != originalValue { 39 | t.Errorf("Expected value to be %d after undo, got %d", originalValue, value) 40 | } 41 | }) 42 | 43 | t.Run("Undo multiple records", func(t *testing.T) { 44 | recorder := &history.BaseRecorder{} 45 | value1, value2 := 10, "test" 46 | originalValue1, originalValue2 := 0, "" 47 | action1 := history.ValueUndoAction(&value1, originalValue1) 48 | action2 := history.ValueUndoAction(&value2, originalValue2) 49 | 50 | recorder.PushAction(action1) 51 | recorder.PushAction(action2) 52 | 53 | recorder.UndoAll() 54 | 55 | if value1 != originalValue1 || value2 != originalValue2 { 56 | t.Errorf("Expected values to be reset after undo, got %d and %s", value1, value2) 57 | } 58 | }) 59 | 60 | t.Run("Undo with no records", func(t *testing.T) { 61 | recorder := &history.BaseRecorder{} 62 | ok := recorder.Undo() // Should not panic 63 | if ok { 64 | t.Error("Expected Undo to return false with no records") 65 | } 66 | }) 67 | } 68 | 69 | func TestValueRecord(t *testing.T) { 70 | t.Run("Integer value", func(t *testing.T) { 71 | value := 5 72 | originalValue := 0 73 | action := history.ValueUndoAction(&value, originalValue) 74 | action.Undo() 75 | if value != originalValue { 76 | t.Errorf("Expected value to be %d after undo, got %d", originalValue, value) 77 | } 78 | }) 79 | 80 | t.Run("String value", func(t *testing.T) { 81 | value := "new" 82 | originalValue := "old" 83 | record := history.ValueUndoAction(&value, originalValue) 84 | record.Undo() 85 | if value != originalValue { 86 | t.Errorf("Expected value to be '%s' after undo, got '%s'", originalValue, value) 87 | } 88 | }) 89 | 90 | t.Run("Struct value", func(t *testing.T) { 91 | type testStruct struct { 92 | field int 93 | } 94 | value := testStruct{field: 10} 95 | originalValue := testStruct{field: 5} 96 | record := history.ValueUndoAction(&value, originalValue) 97 | record.Undo() 98 | if value != originalValue { 99 | t.Errorf("Expected struct to be %+v after undo, got %+v", originalValue, value) 100 | } 101 | }) 102 | } 103 | 104 | func TestRecorder_Interface(t *testing.T) { 105 | var _ history.Recorder = &history.BaseRecorder{} 106 | } 107 | 108 | func TestUndoAction_Interface(t *testing.T) { 109 | var _ history.Action = history.ValueUndoAction(&struct{}{}, struct{}{}) 110 | } 111 | 112 | // Benchmarks 113 | func BenchmarkDefaultRecorder_AddRecord(b *testing.B) { 114 | recorder := &history.BaseRecorder{} 115 | value := 0 116 | b.ResetTimer() 117 | for i := 0; i < b.N; i++ { 118 | recorder.PushAction(history.ValueUndoAction(&value, i)) 119 | } 120 | } 121 | 122 | func BenchmarkDefaultRecorder_Undo(b *testing.B) { 123 | recorder := &history.BaseRecorder{} 124 | value := 0 125 | for i := 0; i < b.N; i++ { 126 | recorder.PushAction(history.ValueUndoAction(&value, i)) 127 | } 128 | b.ResetTimer() 129 | for i := 0; i < b.N; i++ { 130 | recorder.Undo() 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /container/history/set.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "strings" 7 | ) 8 | 9 | // Set is a generic set with elements of type K that supports undo operations. 10 | type Set[K comparable] struct { 11 | recorder Recorder 12 | data map[K]struct{} 13 | } 14 | 15 | // NewSet creates a new Set with the given recorder and initial size. 16 | func NewSet[K comparable](recorder Recorder, size int) *Set[K] { 17 | return &Set[K]{ 18 | recorder: recorder, 19 | data: make(map[K]struct{}, size), 20 | } 21 | } 22 | 23 | // String returns a string representation of the Set. 24 | func (s Set[K]) String() string { 25 | var sb strings.Builder 26 | sb.Grow(len(s.data)*9 + 1) 27 | sb.WriteByte('{') 28 | i := 0 29 | for k := range s.data { 30 | if i > 0 { 31 | sb.WriteByte(',') 32 | } 33 | fmt.Fprint(&sb, k) 34 | i++ 35 | } 36 | sb.WriteByte('}') 37 | return sb.String() 38 | } 39 | 40 | // Clone creates a deep copy of the Set with a new recorder. 41 | func (s *Set[K]) Clone(recorder Recorder) *Set[K] { 42 | return &Set[K]{ 43 | recorder: recorder, 44 | data: maps.Clone(s.data), 45 | } 46 | } 47 | 48 | // Len returns the number of elements in the Set. 49 | func (s *Set[K]) Len() int { 50 | return len(s.data) 51 | } 52 | 53 | // Contains checks if the Set contains the given element. 54 | func (s *Set[K]) Contains(k K) bool { 55 | _, ok := s.data[k] 56 | return ok 57 | } 58 | 59 | // Add adds an element to the Set. 60 | // It returns true if the element was not already present. 61 | func (s *Set[K]) Add(k K) (added bool) { 62 | _, found := s.data[k] 63 | if !found { 64 | s.data[k] = struct{}{} 65 | s.recorder.PushAction(&setUndoAddAction[K]{s: s, k: k}) 66 | added = true 67 | } 68 | return 69 | } 70 | 71 | type setUndoAddAction[K comparable] struct { 72 | s *Set[K] 73 | k K 74 | } 75 | 76 | func (r *setUndoAddAction[K]) Undo() { 77 | delete(r.s.data, r.k) 78 | } 79 | 80 | // Remove removes an element from the Set. 81 | // It returns true if the element was present. 82 | func (s *Set[K]) Remove(k K) (removed bool) { 83 | _, removed = s.data[k] 84 | if removed { 85 | s.recorder.PushAction(&setUndoRemoveAction[K]{s: s, k: k}) 86 | delete(s.data, k) 87 | } 88 | return 89 | } 90 | 91 | type setUndoRemoveAction[K comparable] struct { 92 | s *Set[K] 93 | k K 94 | } 95 | 96 | func (r *setUndoRemoveAction[K]) Undo() { 97 | r.s.data[r.k] = struct{}{} 98 | } 99 | 100 | // Range calls f sequentially for each element in the Set. 101 | // If f returns false, Range stops the iteration. 102 | func (s *Set[K]) Range(f func(K) bool) bool { 103 | for k := range s.data { 104 | if !f(k) { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | 111 | // Clear removes all elements from the Set. 112 | func (s *Set[K]) Clear() { 113 | s.recorder.PushAction(&setUndoClearAction[K]{s: s, values: maps.Clone(s.data)}) 114 | clear(s.data) 115 | } 116 | 117 | type setUndoClearAction[K comparable] struct { 118 | s *Set[K] 119 | values map[K]struct{} 120 | } 121 | 122 | func (r *setUndoClearAction[K]) Undo() { 123 | r.s.data = r.values 124 | } 125 | -------------------------------------------------------------------------------- /container/history/set_iter.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package history 4 | 5 | import ( 6 | "iter" 7 | "maps" 8 | ) 9 | 10 | // All returns an iterator over keys in the set 11 | func (s Set[K]) All() iter.Seq[K] { 12 | return maps.Keys(s.data) 13 | } 14 | -------------------------------------------------------------------------------- /container/history/set_test.go: -------------------------------------------------------------------------------- 1 | package history_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopherd/core/container/history" 7 | ) 8 | 9 | func TestNewSet(t *testing.T) { 10 | recorder := &history.BaseRecorder{} 11 | set := history.NewSet[int](recorder, 10) 12 | if set == nil { 13 | t.Fatal("NewSet returned nil") 14 | } 15 | if set.Len() != 0 { 16 | t.Errorf("Expected empty set, got length %d", set.Len()) 17 | } 18 | } 19 | 20 | func TestSetAdd(t *testing.T) { 21 | recorder := &history.BaseRecorder{} 22 | set := history.NewSet[int](recorder, 10) 23 | 24 | // Test adding a new element 25 | added := set.Add(1) 26 | if !added { 27 | t.Error("Add should return true for a new element") 28 | } 29 | if set.Len() != 1 { 30 | t.Errorf("Expected length 1, got %d", set.Len()) 31 | } 32 | if !set.Contains(1) { 33 | t.Error("Set should contain 1") 34 | } 35 | 36 | // Test adding an existing element 37 | added = set.Add(1) 38 | if added { 39 | t.Error("Add should return false for an existing element") 40 | } 41 | if set.Len() != 1 { 42 | t.Errorf("Expected length 1, got %d", set.Len()) 43 | } 44 | 45 | // Check if the action was recorded 46 | if recorder.Len() != 1 { 47 | t.Errorf("Expected 1 recorded action, got %d", recorder.Len()) 48 | } 49 | } 50 | 51 | func TestSetRemove(t *testing.T) { 52 | recorder := &history.BaseRecorder{} 53 | set := history.NewSet[int](recorder, 10) 54 | set.Add(1) 55 | set.Add(2) 56 | 57 | // Test removing an existing element 58 | removed := set.Remove(1) 59 | if !removed { 60 | t.Error("Remove should return true for an existing element") 61 | } 62 | if set.Len() != 1 { 63 | t.Errorf("Expected length 1, got %d", set.Len()) 64 | } 65 | if set.Contains(1) { 66 | t.Error("Set should not contain 1") 67 | } 68 | 69 | // Test removing a non-existing element 70 | removed = set.Remove(3) 71 | if removed { 72 | t.Error("Remove should return false for a non-existing element") 73 | } 74 | if set.Len() != 1 { 75 | t.Errorf("Expected length 1, got %d", set.Len()) 76 | } 77 | 78 | // Check if the action was recorded 79 | if recorder.Len() != 3 { 80 | t.Errorf("Expected 3 recorded actions, got %d", recorder.Len()) 81 | } 82 | } 83 | 84 | func TestSetClear(t *testing.T) { 85 | recorder := &history.BaseRecorder{} 86 | set := history.NewSet[int](recorder, 10) 87 | set.Add(1) 88 | set.Add(2) 89 | set.Add(3) 90 | 91 | set.Clear() 92 | if set.Len() != 0 { 93 | t.Errorf("Expected empty set after Clear, got length %d", set.Len()) 94 | } 95 | if set.Contains(1) || set.Contains(2) || set.Contains(3) { 96 | t.Error("Set should not contain any elements after Clear") 97 | } 98 | 99 | // Check if the action was recorded 100 | if recorder.Len() != 4 { 101 | t.Errorf("Expected 4 recorded actions, got %d", recorder.Len()) 102 | } 103 | } 104 | 105 | func TestSetRange(t *testing.T) { 106 | set := history.NewSet[int](&history.BaseRecorder{}, 10) 107 | elements := []int{1, 2, 3, 4, 5} 108 | for _, e := range elements { 109 | set.Add(e) 110 | } 111 | 112 | visited := make(map[int]bool) 113 | set.Range(func(k int) bool { 114 | visited[k] = true 115 | return true 116 | }) 117 | 118 | for _, e := range elements { 119 | if !visited[e] { 120 | t.Errorf("Element %d was not visited during Range", e) 121 | } 122 | } 123 | 124 | // Test early termination 125 | count := 0 126 | set.Range(func(k int) bool { 127 | count++ 128 | return count < 3 129 | }) 130 | if count != 3 { 131 | t.Errorf("Range should have terminated after 3 elements, but processed %d", count) 132 | } 133 | } 134 | 135 | func TestSetString(t *testing.T) { 136 | set := history.NewSet[int](&history.BaseRecorder{}, 10) 137 | set.Add(1) 138 | set.Add(2) 139 | set.Add(3) 140 | 141 | str := set.String() 142 | if str != "{1,2,3}" && str != "{1,3,2}" && str != "{2,1,3}" && str != "{2,3,1}" && str != "{3,1,2}" && str != "{3,2,1}" { 143 | t.Errorf("Unexpected string representation: %s", str) 144 | } 145 | } 146 | 147 | func TestSetClone(t *testing.T) { 148 | originalRecorder := &history.BaseRecorder{} 149 | originalSet := history.NewSet[int](originalRecorder, 10) 150 | originalSet.Add(1) 151 | originalSet.Add(2) 152 | 153 | newRecorder := &history.BaseRecorder{} 154 | clonedSet := originalSet.Clone(newRecorder) 155 | 156 | if clonedSet.Len() != originalSet.Len() { 157 | t.Errorf("Cloned set length %d doesn't match original set length %d", clonedSet.Len(), originalSet.Len()) 158 | } 159 | 160 | if !clonedSet.Contains(1) || !clonedSet.Contains(2) { 161 | t.Error("Cloned set doesn't contain all elements from the original set") 162 | } 163 | 164 | // Modify the cloned set 165 | clonedSet.Add(3) 166 | 167 | if originalSet.Contains(3) { 168 | t.Error("Modifying cloned set should not affect the original set") 169 | } 170 | 171 | if newRecorder.Len() != 1 { 172 | t.Errorf("Expected 1 recorded action in the new recorder, got %d", newRecorder.Len()) 173 | } 174 | 175 | if originalRecorder.Len() != 2 { 176 | t.Errorf("Expected 2 recorded actions in the original recorder, got %d", originalRecorder.Len()) 177 | } 178 | } 179 | 180 | func TestSetUndo(t *testing.T) { 181 | recorder := &history.BaseRecorder{} 182 | set := history.NewSet[int](recorder, 10) 183 | 184 | set.Add(1) 185 | set.Add(2) 186 | set.Remove(1) 187 | set.Add(3) 188 | 189 | // Undo Add(3) 190 | if !recorder.Undo() { 191 | t.Error("Undo should return true") 192 | } 193 | if set.Contains(3) { 194 | t.Error("Set should not contain 3 after undoing Add(3)") 195 | } 196 | 197 | // Undo Remove(1) 198 | if !recorder.Undo() { 199 | t.Error("Undo should return true") 200 | } 201 | if !set.Contains(1) { 202 | t.Error("Set should contain 1 after undoing Remove(1)") 203 | } 204 | 205 | // Undo Add(2) 206 | if !recorder.Undo() { 207 | t.Error("Undo should return true") 208 | } 209 | if set.Contains(2) { 210 | t.Error("Set should not contain 2 after undoing Add(2)") 211 | } 212 | 213 | // Undo Add(1) 214 | if !recorder.Undo() { 215 | t.Error("Undo should return true") 216 | } 217 | if set.Contains(1) { 218 | t.Error("Set should not contain 1 after undoing Add(1)") 219 | } 220 | 221 | // Try to undo when there are no more actions 222 | if recorder.Undo() { 223 | t.Error("Undo should return false when there are no more actions") 224 | } 225 | 226 | if set.Len() != 0 { 227 | t.Errorf("Expected empty set after undoing all actions, got length %d", set.Len()) 228 | } 229 | } 230 | 231 | func TestSetUndoAll(t *testing.T) { 232 | recorder := &history.BaseRecorder{} 233 | set := history.NewSet[int](recorder, 10) 234 | 235 | set.Add(1) 236 | set.Add(2) 237 | set.Remove(1) 238 | set.Add(3) 239 | set.Clear() 240 | set.Add(4) 241 | 242 | actionsUndone := recorder.UndoAll() 243 | if actionsUndone != 6 { 244 | t.Errorf("Expected 6 actions undone, got %d", actionsUndone) 245 | } 246 | 247 | if set.Len() != 0 { 248 | t.Errorf("Expected empty set after UndoAll, got length %d", set.Len()) 249 | } 250 | 251 | // Try to undo when there are no more actions 252 | if recorder.Undo() { 253 | t.Error("Undo should return false when there are no more actions") 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /container/history/slice.go: -------------------------------------------------------------------------------- 1 | package history 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "strings" 7 | ) 8 | 9 | // Slice contains a slice data with recorder 10 | type Slice[T any] struct { 11 | recorder Recorder 12 | data []T 13 | } 14 | 15 | // NewSlice creates a new slice with the given recorder, length, and capacity. 16 | func NewSlice[T any](recorder Recorder, len, cap int) *Slice[T] { 17 | return &Slice[T]{ 18 | recorder: recorder, 19 | data: make([]T, len, cap), 20 | } 21 | } 22 | 23 | // String returns a string representation of the Slice. 24 | func (s Slice[T]) String() string { 25 | var sb strings.Builder 26 | sb.Grow(len(s.data)*9 + 1) 27 | sb.WriteByte('[') 28 | for i, x := range s.data { 29 | if i > 0 { 30 | sb.WriteByte(',') 31 | } 32 | fmt.Fprint(&sb, x) 33 | } 34 | sb.WriteByte(']') 35 | return sb.String() 36 | } 37 | 38 | // Clone creates a deep copy of the Slice with a new recorder. 39 | func (s *Slice[T]) Clone(recorder Recorder) *Slice[T] { 40 | return &Slice[T]{ 41 | recorder: recorder, 42 | data: slices.Clone(s.data), 43 | } 44 | } 45 | 46 | // Clip reduces the slice's capacity to match its length. 47 | func (s *Slice[T]) Clip() { 48 | s.data = slices.Clip(s.data) 49 | } 50 | 51 | // Grow increases the slice's capacity, if necessary, to guarantee space for n more elements. 52 | func (s *Slice[T]) Grow(n int) { 53 | s.data = slices.Grow(s.data, n) 54 | } 55 | 56 | // BinarySearch searches for target in the sorted slice and returns its index and a bool indicating if it was found. 57 | func (s *Slice[T]) BinarySearch(target T, cmp func(T, T) int) (int, bool) { 58 | return slices.BinarySearchFunc(s.data, target, cmp) 59 | } 60 | 61 | // Compare compares the slice with another slice using the provided comparison function. 62 | func (s *Slice[T]) Compare(target *Slice[T], cmp func(T, T) int) int { 63 | return slices.CompareFunc(s.data, target.data, cmp) 64 | } 65 | 66 | // Contains reports whether target is present in the slice. 67 | func (s *Slice[T]) Contains(target T, eq func(T, T) bool) bool { 68 | return slices.ContainsFunc(s.data, func(x T) bool { return eq(target, x) }) 69 | } 70 | 71 | // Len returns the length of the slice. 72 | func (s *Slice[T]) Len() int { 73 | return len(s.data) 74 | } 75 | 76 | // Cap returns the capacity of the slice. 77 | func (s *Slice[T]) Cap() int { 78 | return cap(s.data) 79 | } 80 | 81 | // Get returns the i-th element of the slice. 82 | func (s *Slice[T]) Get(i int) T { 83 | return s.data[i] 84 | } 85 | 86 | // Set sets the i-th element of the slice to x. 87 | func (s *Slice[T]) Set(i int, x T) { 88 | s.recorder.PushAction(&sliceUndoSetAction[T]{s: s, i: i, oldValue: s.data[i]}) 89 | s.data[i] = x 90 | } 91 | 92 | type sliceUndoSetAction[T any] struct { 93 | s *Slice[T] 94 | i int 95 | oldValue T 96 | } 97 | 98 | func (r *sliceUndoSetAction[T]) Undo() { 99 | r.s.data[r.i] = r.oldValue 100 | } 101 | 102 | // RemoveAt removes the i-th element from the slice and returns it. 103 | func (s *Slice[T]) RemoveAt(i int) T { 104 | removedValue := s.data[i] 105 | s.recorder.PushAction(&sliceUndoRemoveAtAction[T]{s: s, i: i, value: removedValue}) 106 | s.data = slices.Delete(s.data, i, i+1) 107 | return removedValue 108 | } 109 | 110 | type sliceUndoRemoveAtAction[T any] struct { 111 | s *Slice[T] 112 | i int 113 | value T 114 | } 115 | 116 | func (r *sliceUndoRemoveAtAction[T]) Undo() { 117 | r.s.data = slices.Insert(r.s.data, r.i, r.value) 118 | } 119 | 120 | // Append appends elements to the slice. 121 | func (s *Slice[T]) Append(elements ...T) { 122 | s.recorder.PushAction(&sliceUndoAppendAction[T]{s: s, n: len(elements)}) 123 | s.data = append(s.data, elements...) 124 | } 125 | 126 | type sliceUndoAppendAction[T any] struct { 127 | s *Slice[T] 128 | n int 129 | } 130 | 131 | func (r *sliceUndoAppendAction[T]) Undo() { 132 | r.s.data = r.s.data[:len(r.s.data)-r.n] 133 | } 134 | 135 | // Insert inserts elements at i-th position of the slice. 136 | func (s *Slice[T]) Insert(i int, elements ...T) { 137 | s.recorder.PushAction(&sliceUndoInsertAction[T]{s: s, i: i, n: len(elements)}) 138 | s.data = slices.Insert(s.data, i, elements...) 139 | } 140 | 141 | type sliceUndoInsertAction[T any] struct { 142 | s *Slice[T] 143 | i int 144 | n int 145 | } 146 | 147 | func (r *sliceUndoInsertAction[T]) Undo() { 148 | r.s.data = slices.Delete(r.s.data, r.i, r.i+r.n) 149 | } 150 | 151 | // Reverse reverses the order of elements in the slice. 152 | func (s *Slice[T]) Reverse() { 153 | s.recorder.PushAction(&sliceUndoReverseAction[T]{s: s}) 154 | slices.Reverse(s.data) 155 | } 156 | 157 | type sliceUndoReverseAction[T any] struct { 158 | s *Slice[T] 159 | } 160 | 161 | func (r *sliceUndoReverseAction[T]) Undo() { 162 | slices.Reverse(r.s.data) 163 | } 164 | 165 | // RemoveFirst removes the first occurrence of the specified value from the slice. 166 | // It returns true if the value was found and removed. 167 | func (s *Slice[T]) RemoveFirst(value T, eq func(a, b T) bool) bool { 168 | for i, v := range s.data { 169 | if eq(v, value) { 170 | s.RemoveAt(i) 171 | return true 172 | } 173 | } 174 | return false 175 | } 176 | 177 | // Clear removes all elements from the slice. 178 | func (s *Slice[T]) Clear() { 179 | oldData := slices.Clone(s.data) 180 | s.recorder.PushAction(&sliceUndoClearAction[T]{s: s, oldData: oldData}) 181 | s.data = s.data[:0] 182 | } 183 | 184 | type sliceUndoClearAction[T any] struct { 185 | s *Slice[T] 186 | oldData []T 187 | } 188 | 189 | func (r *sliceUndoClearAction[T]) Undo() { 190 | r.s.data = slices.Clone(r.oldData) 191 | } 192 | -------------------------------------------------------------------------------- /container/history/slice_iter.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package history 4 | 5 | import ( 6 | "iter" 7 | "slices" 8 | ) 9 | 10 | // All returns an iterator over index-value pairs in the slice 11 | func (s Slice[T]) All() iter.Seq2[int, T] { 12 | return slices.All(s.data) 13 | } 14 | 15 | // Backward returns an iterator over index-value pairs in the slice, 16 | func (s Slice[T]) Backward() iter.Seq2[int, T] { 17 | return slices.Backward(s.data) 18 | } 19 | 20 | // Values returns an iterator that yields the slice elements in order. 21 | func (s Slice[T]) Values() iter.Seq[T] { 22 | return slices.Values(s.data) 23 | } 24 | -------------------------------------------------------------------------------- /container/history/slice_test.go: -------------------------------------------------------------------------------- 1 | package history_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopherd/core/container/history" 7 | ) 8 | 9 | func TestNewSlice(t *testing.T) { 10 | recorder := &history.BaseRecorder{} 11 | s := history.NewSlice[int](recorder, 5, 10) 12 | 13 | if s.Len() != 5 { 14 | t.Errorf("Expected length 5, got %d", s.Len()) 15 | } 16 | 17 | // Test initial values 18 | for i := 0; i < s.Len(); i++ { 19 | if s.Get(i) != 0 { 20 | t.Errorf("Expected 0 at index %d, got %d", i, s.Get(i)) 21 | } 22 | } 23 | } 24 | 25 | func TestSliceString(t *testing.T) { 26 | recorder := &history.BaseRecorder{} 27 | s := history.NewSlice[int](recorder, 0, 0) 28 | s.Append(1, 2, 3) 29 | 30 | expected := "[1,2,3]" 31 | if s.String() != expected { 32 | t.Errorf("Expected %s, got %s", expected, s.String()) 33 | } 34 | } 35 | 36 | func TestSliceClone(t *testing.T) { 37 | recorder := &history.BaseRecorder{} 38 | s := history.NewSlice[int](recorder, 0, 0) 39 | s.Append(1, 2, 3) 40 | 41 | newRecorder := &history.BaseRecorder{} 42 | clone := s.Clone(newRecorder) 43 | 44 | if clone.String() != s.String() { 45 | t.Errorf("Clone doesn't match original. Expected %s, got %s", s.String(), clone.String()) 46 | } 47 | 48 | // Modify original, clone should remain unchanged 49 | s.Set(0, 99) 50 | if clone.Get(0) == 99 { 51 | t.Error("Clone was affected by changes to original slice") 52 | } 53 | } 54 | 55 | func TestSliceGrow(t *testing.T) { 56 | recorder := &history.BaseRecorder{} 57 | s := history.NewSlice[int](recorder, 0, 0) 58 | s.Grow(5) 59 | 60 | // Append more elements than the initial capacity 61 | for i := 0; i < 10; i++ { 62 | s.Append(i) 63 | } 64 | 65 | if s.Len() != 10 { 66 | t.Errorf("Expected length 10 after Grow and Append, got %d", s.Len()) 67 | } 68 | } 69 | 70 | func TestSliceBinarySearch(t *testing.T) { 71 | recorder := &history.BaseRecorder{} 72 | s := history.NewSlice[int](recorder, 0, 0) 73 | s.Append(1, 3, 5, 7, 9) 74 | 75 | tests := []struct { 76 | target int 77 | expectedIdx int 78 | expectedFound bool 79 | }{ 80 | {5, 2, true}, 81 | {1, 0, true}, 82 | {9, 4, true}, 83 | {0, 0, false}, 84 | {10, 5, false}, 85 | {6, 3, false}, 86 | } 87 | 88 | for _, tt := range tests { 89 | idx, found := s.BinarySearch(tt.target, func(a, b int) int { return a - b }) 90 | if idx != tt.expectedIdx || found != tt.expectedFound { 91 | t.Errorf("BinarySearch(%d) = (%d, %v), expected (%d, %v)", 92 | tt.target, idx, found, tt.expectedIdx, tt.expectedFound) 93 | } 94 | } 95 | } 96 | 97 | func TestSliceCompare(t *testing.T) { 98 | recorder := &history.BaseRecorder{} 99 | s1 := history.NewSlice[int](recorder, 0, 0) 100 | s2 := history.NewSlice[int](recorder, 0, 0) 101 | 102 | s1.Append(1, 2, 3) 103 | s2.Append(1, 2, 3) 104 | 105 | if cmp := s1.Compare(s2, func(a, b int) int { return a - b }); cmp != 0 { 106 | t.Errorf("Expected Compare to return 0, got %d", cmp) 107 | } 108 | 109 | s2.Set(2, 4) 110 | if cmp := s1.Compare(s2, func(a, b int) int { return a - b }); cmp >= 0 { 111 | t.Errorf("Expected Compare to return negative value, got %d", cmp) 112 | } 113 | 114 | s1.Set(0, 0) 115 | if cmp := s1.Compare(s2, func(a, b int) int { return a - b }); cmp >= 0 { 116 | t.Errorf("Expected Compare to return negative value, got %d", cmp) 117 | } 118 | } 119 | 120 | func TestSliceContains(t *testing.T) { 121 | recorder := &history.BaseRecorder{} 122 | s := history.NewSlice[int](recorder, 0, 0) 123 | s.Append(1, 2, 3, 4, 5) 124 | 125 | if !s.Contains(3, func(a, b int) bool { return a == b }) { 126 | t.Error("Expected Contains(3) to return true") 127 | } 128 | 129 | if s.Contains(6, func(a, b int) bool { return a == b }) { 130 | t.Error("Expected Contains(6) to return false") 131 | } 132 | } 133 | 134 | func TestSliceSet(t *testing.T) { 135 | recorder := &history.BaseRecorder{} 136 | s := history.NewSlice[int](recorder, 3, 3) 137 | 138 | s.Set(1, 42) 139 | if s.Get(1) != 42 { 140 | t.Errorf("Expected 42 at index 1, got %d", s.Get(1)) 141 | } 142 | 143 | // Test undo 144 | recorder.Undo() 145 | if s.Get(1) != 0 { 146 | t.Errorf("Expected 0 at index 1 after undo, got %d", s.Get(1)) 147 | } 148 | } 149 | 150 | func TestSliceRemoveAt(t *testing.T) { 151 | recorder := &history.BaseRecorder{} 152 | s := history.NewSlice[int](recorder, 0, 0) 153 | s.Append(1, 2, 3, 4, 5) 154 | 155 | removed := s.RemoveAt(2) 156 | if removed != 3 { 157 | t.Errorf("Expected removed value to be 3, got %d", removed) 158 | } 159 | if s.Len() != 4 { 160 | t.Errorf("Expected length 4 after RemoveAt, got %d", s.Len()) 161 | } 162 | if s.Get(2) != 4 { 163 | t.Errorf("Expected 4 at index 2 after RemoveAt, got %d", s.Get(2)) 164 | } 165 | 166 | // Test undo 167 | recorder.Undo() 168 | if s.Len() != 5 { 169 | t.Errorf("Expected length 5 after undo, got %d", s.Len()) 170 | } 171 | if s.Get(2) != 3 { 172 | t.Errorf("Expected 3 at index 2 after undo, got %d", s.Get(2)) 173 | } 174 | } 175 | 176 | func TestSliceAppend(t *testing.T) { 177 | recorder := &history.BaseRecorder{} 178 | s := history.NewSlice[int](recorder, 0, 0) 179 | 180 | s.Append(1, 2, 3) 181 | if s.Len() != 3 { 182 | t.Errorf("Expected length 3 after Append, got %d", s.Len()) 183 | } 184 | 185 | // Test undo 186 | recorder.Undo() 187 | if s.Len() != 0 { 188 | t.Errorf("Expected length 0 after undo, got %d", s.Len()) 189 | } 190 | } 191 | 192 | func TestSliceInsert(t *testing.T) { 193 | recorder := &history.BaseRecorder{} 194 | s := history.NewSlice[int](recorder, 0, 0) 195 | s.Append(1, 2, 5) 196 | 197 | s.Insert(2, 3, 4) 198 | if s.Len() != 5 { 199 | t.Errorf("Expected length 5 after Insert, got %d", s.Len()) 200 | } 201 | if s.Get(2) != 3 || s.Get(3) != 4 { 202 | t.Errorf("Insert didn't place elements correctly") 203 | } 204 | 205 | // Test undo 206 | recorder.Undo() 207 | if s.Len() != 3 { 208 | t.Errorf("Expected length 3 after undo, got %d", s.Len()) 209 | } 210 | if s.Get(2) != 5 { 211 | t.Errorf("Expected 5 at index 2 after undo, got %d", s.Get(2)) 212 | } 213 | } 214 | 215 | func TestSliceReverse(t *testing.T) { 216 | recorder := &history.BaseRecorder{} 217 | s := history.NewSlice[int](recorder, 0, 0) 218 | s.Append(1, 2, 3, 4, 5) 219 | 220 | s.Reverse() 221 | for i := 0; i < s.Len(); i++ { 222 | if s.Get(i) != 5-i { 223 | t.Errorf("Reverse failed: expected %d at index %d, got %d", 5-i, i, s.Get(i)) 224 | } 225 | } 226 | 227 | // Test undo 228 | recorder.Undo() 229 | for i := 0; i < s.Len(); i++ { 230 | if s.Get(i) != i+1 { 231 | t.Errorf("Undo Reverse failed: expected %d at index %d, got %d", i+1, i, s.Get(i)) 232 | } 233 | } 234 | } 235 | 236 | func TestSliceRemoveFirst(t *testing.T) { 237 | recorder := &history.BaseRecorder{} 238 | s := history.NewSlice[int](recorder, 0, 0) 239 | s.Append(1, 2, 3, 2, 4) 240 | 241 | removed := s.RemoveFirst(2, func(a, b int) bool { return a == b }) 242 | if !removed { 243 | t.Error("RemoveFirst should return true for existing element") 244 | } 245 | if s.Len() != 4 { 246 | t.Errorf("Expected length 4 after RemoveFirst, got %d", s.Len()) 247 | } 248 | if s.Get(1) != 3 { 249 | t.Errorf("Expected 3 at index 1 after RemoveFirst, got %d", s.Get(1)) 250 | } 251 | 252 | removed = s.RemoveFirst(5, func(a, b int) bool { return a == b }) 253 | if removed { 254 | t.Error("RemoveFirst should return false for non-existing element") 255 | } 256 | 257 | // Test undo 258 | recorder.Undo() 259 | if s.Len() != 5 { 260 | t.Errorf("Expected length 5 after undo, got %d", s.Len()) 261 | } 262 | if s.Get(1) != 2 { 263 | t.Errorf("Expected 2 at index 1 after undo, got %d", s.Get(1)) 264 | } 265 | } 266 | 267 | func TestSliceClear(t *testing.T) { 268 | recorder := &history.BaseRecorder{} 269 | s := history.NewSlice[int](recorder, 0, 0) 270 | s.Append(1, 2, 3, 4, 5) 271 | 272 | s.Clear() 273 | if s.Len() != 0 { 274 | t.Errorf("Expected length 0 after Clear, got %d", s.Len()) 275 | } 276 | 277 | // Test undo 278 | recorder.Undo() 279 | if s.Len() != 5 { 280 | t.Errorf("Expected length 5 after undo, got %d", s.Len()) 281 | } 282 | for i := 0; i < s.Len(); i++ { 283 | if s.Get(i) != i+1 { 284 | t.Errorf("Undo Clear failed: expected %d at index %d, got %d", i+1, i, s.Get(1)) 285 | } 286 | } 287 | } 288 | 289 | func TestSliceClip(t *testing.T) { 290 | recorder := &history.BaseRecorder{} 291 | s := history.NewSlice[int](recorder, 3, 10) 292 | 293 | initialCap := s.Cap() 294 | if initialCap != 10 { 295 | t.Errorf("Expected initial capacity 10, got %d", initialCap) 296 | } 297 | 298 | s.Append(4, 5) 299 | s.Clip() 300 | 301 | finalCap := s.Cap() 302 | if finalCap != 5 { 303 | t.Errorf("Expected final capacity 5 after Clip, got %d", finalCap) 304 | } 305 | 306 | if s.Len() != 5 { 307 | t.Errorf("Expected length 5 after Clip, got %d", s.Len()) 308 | } 309 | 310 | expectedValues := []int{0, 0, 0, 4, 5} 311 | for i, v := range expectedValues { 312 | if s.Get(i) != v { 313 | t.Errorf("Expected %d at index %d after Clip, got %d", v, i, s.Get(i)) 314 | } 315 | } 316 | 317 | // Test that Clip is not undoable 318 | recorder.Undo() 319 | if s.Cap() != 5 { 320 | t.Errorf("Expected capacity to remain 5 after undo, got %d", s.Cap()) 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /container/iters/README.md: -------------------------------------------------------------------------------- 1 | # Go 1.23 Iterators: A Deep Dive 2 | 3 | ## Introduction 4 | 5 | Go 1.23 introduces a game-changing feature to the language: built-in support for iterators. This addition brings a more functional and flexible approach to working with sequences of data, aligning Go with modern programming paradigms while maintaining its trademark simplicity and efficiency. In this comprehensive guide, we'll explore the new `iter` package, diving deep into its core concepts, practical applications, and some of the more advanced functions it offers. 6 | 7 | ## Understanding Iterators in Go 1.23 8 | 9 | At its core, an iterator in Go 1.23 is a function that yields successive elements of a sequence to a callback function. The `iter` package defines two main types of iterators: 10 | 11 | 1. `Seq[V any]`: For sequences of single values 12 | 2. `Seq2[K, V any]`: For sequences of key-value pairs 13 | 14 | These iterators are defined as function types: 15 | 16 | ```go 17 | type Seq[V any] func(yield func(V) bool) 18 | type Seq2[K, V any] func(yield func(K, V) bool) 19 | ``` 20 | 21 | The `yield` function is called for each element in the sequence. It returns a boolean indicating whether the iteration should continue (true) or stop (false). 22 | 23 | ## Basic Usage 24 | 25 | Let's start with a simple example to illustrate how to use these iterators: 26 | 27 | ```go 28 | func PrintAll[V any](seq iter.Seq[V]) { 29 | for v := range seq { 30 | fmt.Println(v) 31 | } 32 | } 33 | ``` 34 | 35 | This function takes a `Seq[V]` and prints all its elements. The `range` keyword is overloaded in Go 1.23 to work with iterators, making their usage intuitive and familiar. 36 | 37 | ## Creating Iterators 38 | 39 | The [github.com/gopherd/core/container/iters]() package provides several utility functions to create and manipulate iterators. Let's look at some of them: 40 | 41 | ### Enumerate 42 | 43 | The `Enumerate` function creates an iterator from a slice: 44 | 45 | ```go 46 | func Enumerate[S ~[]E, E any](s S) iter.Seq[E] { 47 | return func(yield func(E) bool) { 48 | for _, v := range s { 49 | if !yield(v) { 50 | return 51 | } 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | Usage: 58 | 59 | ```go 60 | for v := range iters.Enumerate([]string{"a", "b", "c"}) { 61 | fmt.Println(v) // Output: a \n b \n c 62 | } 63 | ``` 64 | 65 | ### Range 66 | 67 | The `Range` function generates a sequence of numbers: 68 | 69 | ```go 70 | func Range[T cmp.Ordered](start, end, step T) iter.Seq[T] { 71 | // ... (error checking omitted for brevity) 72 | return func(yield func(T) bool) { 73 | if start < end { 74 | for i := start; i < end; i += step { 75 | if !yield(i) { 76 | return 77 | } 78 | } 79 | } else { 80 | for i := start; i > end; i += step { 81 | if !yield(i) { 82 | return 83 | } 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | Usage: 91 | 92 | ```go 93 | for v := range iters.Range(1, 10, 2) { 94 | fmt.Println(v) // Output: 1 3 5 7 9 95 | } 96 | ``` 97 | 98 | ## Advanced Iterator Functions 99 | 100 | Now, let's dive into some of the more complex functions provided by the `iters` package. 101 | 102 | ### Zip 103 | 104 | The `Zip` function combines two sequences into a single sequence of pairs: 105 | 106 | ```go 107 | func Zip[T any, U any](s1 iter.Seq[T], s2 iter.Seq[U]) iter.Seq2[T, U] { 108 | return func(yield func(T, U) bool) { 109 | next, stop := iter.Pull(s2) 110 | defer stop() 111 | for v1 := range s1 { 112 | v2, _ := next() 113 | if !yield(v1, v2) { 114 | return 115 | } 116 | } 117 | var zero1 T 118 | for { 119 | if v2, ok := next(); !ok { 120 | return 121 | } else if !yield(zero1, v2) { 122 | return 123 | } 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | This function is particularly interesting because it demonstrates how to work with two iterators simultaneously. It uses the `Pull` function to convert `s2` into a pull-style iterator, allowing for more control over the iteration process. 130 | 131 | The `Zip` function continues until both input sequences are exhausted. If one sequence is longer than the other, the remaining elements are paired with zero values of the other type. 132 | 133 | Usage: 134 | 135 | ```go 136 | seq1 := iters.Enumerate([]int{1, 2, 3}) 137 | seq2 := iters.Enumerate([]string{"a", "b", "c", "d"}) 138 | for v1, v2 := range iters.Zip(seq1, seq2) { 139 | fmt.Printf("(%d, %s)\n", v1, v2) 140 | } 141 | // Output: 142 | // (1, a) 143 | // (2, b) 144 | // (3, c) 145 | // (0, d) 146 | ``` 147 | 148 | ### Split 149 | 150 | The `Split` function divides a slice into multiple chunks: 151 | 152 | ```go 153 | func Split[S ~[]T, T any](s S, n int) iter.Seq[[]T] { 154 | if n < 1 { 155 | panic("n must be positive") 156 | } 157 | return func(yield func([]T) bool) { 158 | total := len(s) 159 | size := total / n 160 | remainder := total % n 161 | i := 0 162 | for i < total { 163 | var chunk []T 164 | if remainder > 0 { 165 | chunk = s[i : i+size+1] 166 | remainder-- 167 | i += size + 1 168 | } else { 169 | chunk = s[i : i+size] 170 | i += size 171 | } 172 | if !yield(chunk) { 173 | return 174 | } 175 | } 176 | } 177 | } 178 | ``` 179 | 180 | This function is useful when you need to process a large slice in smaller, more manageable pieces. It ensures that the chunks are as evenly sized as possible, distributing any remainder elements among the first few chunks. 181 | 182 | Usage: 183 | 184 | ```go 185 | data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 186 | for chunk := range iters.Split(data, 3) { 187 | fmt.Println(chunk) 188 | } 189 | // Output: 190 | // [1 2 3 4] 191 | // [5 6 7] 192 | // [8 9 10] 193 | ``` 194 | 195 | ### GroupBy 196 | 197 | The `GroupBy` function is a powerful tool for organizing data based on a key function: 198 | 199 | ```go 200 | func GroupBy[K comparable, V any](s iter.Seq[V], f func(V) K) iter.Seq2[K, []V] { 201 | return func(yield func(K, []V) bool) { 202 | groups := make(map[K][]V) 203 | for v := range s { 204 | k := f(v) 205 | groups[k] = append(groups[k], v) 206 | } 207 | for k, vs := range groups { 208 | if !yield(k, vs) { 209 | return 210 | } 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | This function takes a sequence and a key function. It groups the elements of the sequence based on the keys produced by the key function. The result is a sequence of key-value pairs, where each key is associated with a slice of all elements that produced that key. 217 | 218 | Usage: 219 | 220 | ```go 221 | data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 222 | evenOdd := func(n int) string { 223 | if n%2 == 0 { 224 | return "even" 225 | } 226 | return "odd" 227 | } 228 | for key, group := range iters.GroupBy(iters.Enumerate(data), evenOdd) { 229 | fmt.Printf("%s: %v\n", key, group) 230 | } 231 | // Output: 232 | // odd: [1 3 5 7 9] 233 | // even: [2 4 6 8 10] 234 | ``` 235 | 236 | ## The Power of Pull 237 | 238 | The `iter` package introduces a powerful concept called "Pull", which converts a push-style iterator into a pull-style iterator. This is particularly useful when you need more control over the iteration process. 239 | 240 | ```go 241 | func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) 242 | ``` 243 | 244 | The `Pull` function returns two functions: 245 | 1. `next`: Returns the next value in the sequence and a boolean indicating if the value is valid. 246 | 2. `stop`: Ends the iteration. 247 | 248 | This allows for more complex iteration patterns, as demonstrated in the `Zip` function we saw earlier. 249 | 250 | ## Conclusion 251 | 252 | Go 1.23's introduction of iterators marks a significant evolution in the language's capabilities for handling sequences of data. The `iter` package provides a rich set of tools for creating, manipulating, and consuming iterators, enabling more expressive and functional programming styles while maintaining Go's simplicity and performance. 253 | 254 | From basic operations like `Enumerate` and `Range`, to more complex functions like `Zip`, `Split`, and `GroupBy`, the new iterator system offers powerful abstractions for working with data sequences. The introduction of pull-style iterators through the `Pull` function further extends the flexibility and control available to developers. 255 | 256 | As you incorporate these new features into your Go programs, you'll likely find new, more elegant solutions to common programming problems. The iterator system in Go 1.23 opens up exciting possibilities for data processing, functional programming, and beyond. 257 | 258 | Remember, while iterators provide powerful abstractions, they should be used judiciously. In many cases, simple loops or slices may still be the most readable and performant solution. As with all features, the key is to understand the tools available and choose the right one for each specific task. 259 | 260 | Happy iterating! 261 | -------------------------------------------------------------------------------- /container/iters/generator.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | // Package iters provides utility functions for working with iterators and sequences. 4 | package iters 5 | 6 | import ( 7 | "cmp" 8 | "iter" 9 | 10 | "github.com/gopherd/core/constraints" 11 | ) 12 | 13 | // Enumerate returns an iterator that generates a sequence of values for each element 14 | // in the slice. The index of the element is not provided. If you need the index, use 15 | // slices.All instead. 16 | // 17 | // Example: 18 | // 19 | // for v := range Enumerate([]string{"a", "b", "c"}) { 20 | // fmt.Println(v) // Output: a \n b \n c 21 | // } 22 | func Enumerate[S ~[]E, E any](s S) iter.Seq[E] { 23 | return func(yield func(E) bool) { 24 | for _, v := range s { 25 | if !yield(v) { 26 | return 27 | } 28 | } 29 | } 30 | } 31 | 32 | // List returns an iterator that generates a sequence of the provided values. 33 | func List[T any](values ...T) iter.Seq[T] { 34 | return func(yield func(T) bool) { 35 | for _, v := range values { 36 | if !yield(v) { 37 | return 38 | } 39 | } 40 | } 41 | } 42 | 43 | // Infinite returns an iterator that generates an infinite sequence of integers starting from 0. 44 | func Infinite() iter.Seq[int] { 45 | return func(yield func(int) bool) { 46 | for i := 0; ; i++ { 47 | if !yield(i) { 48 | return 49 | } 50 | } 51 | } 52 | } 53 | 54 | // Repeat returns an iterator that generates a sequence of n elements, each with the value v. 55 | // It panics if n is negative. 56 | func Repeat[T any](v T, n int) iter.Seq[T] { 57 | if n < 0 { 58 | panic("n must be non-negative") 59 | } 60 | return func(yield func(T) bool) { 61 | for i := 0; i < n; i++ { 62 | if !yield(v) { 63 | return 64 | } 65 | } 66 | } 67 | } 68 | 69 | // LessThan returns an iterator that generates a sequence of numbers [0, end) with a step size of 1. 70 | // It panics if end is negative. 71 | func LessThan[T constraints.Real](end T) iter.Seq[T] { 72 | if end < 0 { 73 | panic("end must be non-negative") 74 | } 75 | return func(yield func(T) bool) { 76 | for i := T(0); i < end; i++ { 77 | if !yield(i) { 78 | return 79 | } 80 | } 81 | } 82 | } 83 | 84 | // Range returns an iterator that generates a sequence of numbers from start to end 85 | // with a step size of step. The sequence includes start but excludes end. 86 | // 87 | // It panics if step is zero, or if start < end and step is negative, 88 | // or if start > end and step is positive. 89 | // 90 | // Example: 91 | // 92 | // for v := range Range(1, 10, 2) { 93 | // fmt.Println(v) // Output: 1 3 5 7 9 94 | // } 95 | func Range[T cmp.Ordered](start, end, step T) iter.Seq[T] { 96 | var zero T 97 | if step == zero { 98 | panic("step cannot be zero") 99 | } 100 | if start < end && step < zero { 101 | panic("step must be positive when start < end") 102 | } else if start > end && step > zero { 103 | panic("step must be negative when start > end") 104 | } 105 | return func(yield func(T) bool) { 106 | if start < end { 107 | for i := start; i < end; i += step { 108 | if !yield(i) { 109 | return 110 | } 111 | } 112 | } else { 113 | for i := start; i > end; i += step { 114 | if !yield(i) { 115 | return 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Steps returns an iterator that generates a sequence of n numbers. 123 | // The behavior of the sequence depends on the provided start value and optional steps. 124 | // 125 | // Parameters: 126 | // - n: The number of elements to generate in the sequence. 127 | // - start: The starting value of the sequence. 128 | // - steps: Optional variadic parameter defining the increments for the sequence. 129 | // 130 | // If no steps are provided, it generates a sequence starting from 'start' and incrementing by 1 each time. 131 | // If steps are provided, it generates a sequence starting from 'start', then repeatedly applying 132 | // the steps in a cyclic manner to generate subsequent values. 133 | // 134 | // It panics if n is negative. 135 | // 136 | // Examples: 137 | // 138 | // // No steps provided (increment by 1): 139 | // for v := range Steps(5, 10) { 140 | // fmt.Print(v, " ") // Output: 10 11 12 13 14 141 | // } 142 | // 143 | // // Single step provided: 144 | // for v := range Steps(5, 1, 2) { 145 | // fmt.Print(v, " ") // Output: 1 3 5 7 9 146 | // } 147 | // 148 | // // Multiple steps provided: 149 | // for v := range Steps(6, 1, 2, 3, 4) { 150 | // fmt.Print(v, " ") // Output: 1 3 6 10 12 15 151 | // } 152 | // 153 | // // Using negative steps: 154 | // for v := range Steps(5, 20, -1, -2, -3) { 155 | // fmt.Print(v, " ") // Output: 20 19 17 14 13 156 | // } 157 | func Steps[T constraints.Number](n int, start T, steps ...T) iter.Seq[T] { 158 | if n < 0 { 159 | panic("n must be non-negative") 160 | } 161 | return func(yield func(T) bool) { 162 | if len(steps) == 0 { 163 | for i := 0; i < n; i++ { 164 | if !yield(start) { 165 | return 166 | } 167 | start++ 168 | } 169 | return 170 | } 171 | for i := 0; i < n; i++ { 172 | if !yield(start) { 173 | return 174 | } 175 | start += steps[i%len(steps)] 176 | } 177 | } 178 | } 179 | 180 | // Split returns an iterator that generates a sequence of chunks from s. 181 | // The sequence is split into n chunks of approximately equal size. 182 | // It panics if n is less than 1. 183 | func Split[S ~[]T, T any](s S, n int) iter.Seq[[]T] { 184 | if n < 1 { 185 | panic("n must be positive") 186 | } 187 | return func(yield func([]T) bool) { 188 | total := len(s) 189 | size := total / n 190 | remainder := total % n 191 | i := 0 192 | for i < total { 193 | var chunk []T 194 | if remainder > 0 { 195 | chunk = s[i : i+size+1] 196 | remainder-- 197 | i += size + 1 198 | } else { 199 | chunk = s[i : i+size] 200 | i += size 201 | } 202 | if !yield(chunk) { 203 | return 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /container/iters/iters.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | // Package iters provides utility functions for working with iterators and sequences. 4 | package iters 5 | 6 | import ( 7 | "cmp" 8 | "iter" 9 | "slices" 10 | 11 | "github.com/gopherd/core/container/pair" 12 | ) 13 | 14 | // Sort returns an iterator that generates a sorted sequence of the elements in s. 15 | func Sort[T cmp.Ordered](s iter.Seq[T]) iter.Seq[T] { 16 | return SortFunc(s, cmp.Compare) 17 | } 18 | 19 | // SortFunc returns an iterator that generates a sorted sequence of the elements in s using the comparison function. 20 | func SortFunc[T any](s iter.Seq[T], cmp func(T, T) int) iter.Seq[T] { 21 | return func(yield func(T) bool) { 22 | s := slices.Collect(s) 23 | slices.SortFunc(s, cmp) 24 | for _, v := range s { 25 | if !yield(v) { 26 | return 27 | } 28 | } 29 | } 30 | } 31 | 32 | // Sort2 returns an iterator that generates a sorted sequence of the key-value pairs in m. 33 | func Sort2[K, V cmp.Ordered](m iter.Seq2[K, V]) iter.Seq2[K, V] { 34 | return SortFunc2(m, pair.Compare) 35 | } 36 | 37 | // SortFunc2 returns an iterator that generates a sorted sequence of the key-value pairs in m using the comparison function. 38 | func SortFunc2[K, V any](m iter.Seq2[K, V], cmp func(pair.Pair[K, V], pair.Pair[K, V]) int) iter.Seq2[K, V] { 39 | return func(yield func(K, V) bool) { 40 | s := Collect2(m) 41 | slices.SortFunc(s, cmp) 42 | for _, p := range s { 43 | if !yield(p.First, p.Second) { 44 | return 45 | } 46 | } 47 | } 48 | } 49 | 50 | // SortKeys returns an iterator that generates a sorted sequence of the key-value pairs in m by key. 51 | func SortKeys[K cmp.Ordered, V any](m iter.Seq2[K, V]) iter.Seq2[K, V] { 52 | return SortFunc2(m, pair.CompareFirst) 53 | } 54 | 55 | // SortValues returns an iterator that generates a sorted sequence of the key-value pairs in m by value. 56 | func SortValues[K any, V cmp.Ordered](m iter.Seq2[K, V]) iter.Seq2[K, V] { 57 | return SortFunc2(m, pair.CompareSecond) 58 | } 59 | 60 | // Zip returns an iterator that generates pairs of elements from s1 and s2. 61 | // If one sequence is longer, remaining elements are paired with zero values. 62 | func Zip[T any, U any](s1 iter.Seq[T], s2 iter.Seq[U]) iter.Seq2[T, U] { 63 | return func(yield func(T, U) bool) { 64 | next, stop := iter.Pull(s2) 65 | defer stop() 66 | for v1 := range s1 { 67 | v2, _ := next() 68 | if !yield(v1, v2) { 69 | return 70 | } 71 | } 72 | var zero1 T 73 | for { 74 | if v2, ok := next(); !ok { 75 | return 76 | } else if !yield(zero1, v2) { 77 | return 78 | } 79 | } 80 | } 81 | } 82 | 83 | // Concat returns an iterator that generates a sequence of elements from all input sequences. 84 | func Concat[T any](ss ...iter.Seq[T]) iter.Seq[T] { 85 | return func(yield func(T) bool) { 86 | for _, s := range ss { 87 | for v := range s { 88 | if !yield(v) { 89 | return 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | // Concat2 returns an iterator that generates a sequence of key-value pairs from all input sequences. 97 | func Concat2[K, V any](ms ...iter.Seq2[K, V]) iter.Seq2[K, V] { 98 | return func(yield func(K, V) bool) { 99 | for _, m := range ms { 100 | for k, v := range m { 101 | if !yield(k, v) { 102 | return 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | // WithIndex returns an iterator that generates a sequence of index-value pairs from s. 110 | func WithIndex[T any](s iter.Seq[T]) iter.Seq2[int, T] { 111 | return func(yield func(int, T) bool) { 112 | i := 0 113 | for v := range s { 114 | if !yield(i, v) { 115 | return 116 | } 117 | i++ 118 | } 119 | } 120 | } 121 | 122 | // Keys returns an iterator that generates a sequence of keys from the key-value sequence m. 123 | func Keys[K any, V any](m iter.Seq2[K, V]) iter.Seq[K] { 124 | return func(yield func(K) bool) { 125 | for k := range m { 126 | if !yield(k) { 127 | return 128 | } 129 | } 130 | } 131 | } 132 | 133 | // Values returns an iterator that generates a sequence of values from the key-value sequence m. 134 | func Values[K any, V any](m iter.Seq2[K, V]) iter.Seq[V] { 135 | return func(yield func(V) bool) { 136 | for _, v := range m { 137 | if !yield(v) { 138 | return 139 | } 140 | } 141 | } 142 | } 143 | 144 | // Unique returns an iterator that generates a sequence of unique elements from s. 145 | // Adjacent duplicate elements are removed. The sequence must be sorted. If not, use 146 | // the Sort function first or the Distinct function directly. 147 | func Unique[T comparable](s iter.Seq[T]) iter.Seq[T] { 148 | var last T 149 | var first = true 150 | return func(yield func(T) bool) { 151 | for v := range s { 152 | if first || v != last { 153 | first = false 154 | last = v 155 | if !yield(v) { 156 | return 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | // UniqueFunc returns an iterator that generates a sequence of unique elements from s. 164 | // Adjacent elements are considered duplicates if the function f returns the same value for them. 165 | // The sequence must be sorted. If not, use the SortFunc first or the DistinctFunc directly. 166 | func UniqueFunc[F ~func(T, T) bool, T comparable](s iter.Seq[T], eq F) iter.Seq[T] { 167 | var last T 168 | var first = true 169 | return func(yield func(T) bool) { 170 | for v := range s { 171 | if first || !eq(v, last) { 172 | first = false 173 | last = v 174 | if !yield(v) { 175 | return 176 | } 177 | } 178 | } 179 | } 180 | } 181 | 182 | // Distinct returns an iterator that generates a sequence of distinct elements from s. 183 | func Distinct[T comparable](s iter.Seq[T]) iter.Seq[T] { 184 | return func(yield func(T) bool) { 185 | seen := make(map[T]struct{}) 186 | for v := range s { 187 | if _, ok := seen[v]; !ok { 188 | seen[v] = struct{}{} 189 | if !yield(v) { 190 | return 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | // DistinctFunc returns an iterator that generates a sequence of distinct elements from s. 198 | func DistinctFunc[F ~func(T) K, T any, K comparable](s iter.Seq[T], f F) iter.Seq[K] { 199 | return func(yield func(K) bool) { 200 | seen := make(map[K]struct{}) 201 | for v := range s { 202 | k := f(v) 203 | if _, ok := seen[k]; !ok { 204 | seen[k] = struct{}{} 205 | if !yield(k) { 206 | return 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | // Map returns an iterator that applies the function f to each element in s. 214 | func Map[T, U any](s iter.Seq[T], f func(T) U) iter.Seq[U] { 215 | return func(yield func(U) bool) { 216 | for v := range s { 217 | if !yield(f(v)) { 218 | return 219 | } 220 | } 221 | } 222 | } 223 | 224 | // Map2 returns an iterator that applies the function f to each key-value pair in m. 225 | func Map2[K, V, U any](m iter.Seq2[K, V], f func(K, V) U) iter.Seq[U] { 226 | return func(yield func(U) bool) { 227 | for k, v := range m { 228 | if !yield(f(k, v)) { 229 | return 230 | } 231 | } 232 | } 233 | } 234 | 235 | // Filter returns an iterator that generates a sequence of elements from s 236 | // for which the function f returns true. 237 | func Filter[T any](s iter.Seq[T], f func(T) bool) iter.Seq[T] { 238 | return func(yield func(T) bool) { 239 | for v := range s { 240 | if f(v) { 241 | if !yield(v) { 242 | return 243 | } 244 | } 245 | } 246 | } 247 | } 248 | 249 | // Filter2 returns an iterator that generates a sequence of key-value pairs from m 250 | // for which the function f returns true. 251 | func Filter2[K, V any](m iter.Seq2[K, V], f func(K, V) bool) iter.Seq2[K, V] { 252 | return func(yield func(K, V) bool) { 253 | for k, v := range m { 254 | if f(k, v) { 255 | if !yield(k, v) { 256 | return 257 | } 258 | } 259 | } 260 | } 261 | } 262 | 263 | // GroupBy returns an iterator that generates a sequence of key-value pairs, 264 | // where the key is the result of applying the function f to each element in s, 265 | // and the value is a slice of all elements in s that produced that key. 266 | func GroupBy[K comparable, V any](s iter.Seq[V], f func(V) K) iter.Seq2[K, []V] { 267 | return func(yield func(K, []V) bool) { 268 | groups := make(map[K][]V) 269 | for v := range s { 270 | k := f(v) 271 | groups[k] = append(groups[k], v) 272 | } 273 | for k, vs := range groups { 274 | if !yield(k, vs) { 275 | return 276 | } 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /container/pair/pair.go: -------------------------------------------------------------------------------- 1 | // Package pair provides a generic Pair type for holding two values of any types. 2 | package pair 3 | 4 | import ( 5 | "cmp" 6 | "fmt" 7 | ) 8 | 9 | // Pair represents a tuple of two values of potentially different types. 10 | type Pair[T1, T2 any] struct { 11 | First T1 12 | Second T2 13 | } 14 | 15 | // New creates a new Pair with the given values. 16 | func New[T1, T2 any](first T1, second T2) Pair[T1, T2] { 17 | return Pair[T1, T2]{First: first, Second: second} 18 | } 19 | 20 | func (p Pair[T1, T2]) String() string { 21 | return fmt.Sprintf("(%v,%v)", p.First, p.Second) 22 | } 23 | 24 | func Compare[T1, T2 cmp.Ordered](p1, p2 Pair[T1, T2]) int { 25 | if c := cmp.Compare(p1.First, p2.First); c != 0 { 26 | return c 27 | } 28 | return cmp.Compare(p1.Second, p2.Second) 29 | } 30 | 31 | func CompareFirst[T1 cmp.Ordered, T2 any](p1, p2 Pair[T1, T2]) int { 32 | return cmp.Compare(p1.First, p2.First) 33 | } 34 | 35 | func CompareSecond[T1 any, T2 cmp.Ordered](p1, p2 Pair[T1, T2]) int { 36 | return cmp.Compare(p1.Second, p2.Second) 37 | } 38 | -------------------------------------------------------------------------------- /container/pair/pair_test.go: -------------------------------------------------------------------------------- 1 | package pair_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/gopherd/core/container/pair" 9 | ) 10 | 11 | func TestPair(t *testing.T) { 12 | t.Run("New", func(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | first any 16 | second any 17 | expected pair.Pair[any, any] 18 | }{ 19 | { 20 | name: "int and string", 21 | first: 42, 22 | second: "hello", 23 | expected: pair.Pair[any, any]{First: 42, Second: "hello"}, 24 | }, 25 | { 26 | name: "float and bool", 27 | first: 3.14, 28 | second: true, 29 | expected: pair.Pair[any, any]{First: 3.14, Second: true}, 30 | }, 31 | { 32 | name: "string and nil", 33 | first: "test", 34 | second: nil, 35 | expected: pair.Pair[any, any]{First: "test", Second: nil}, 36 | }, 37 | { 38 | name: "nil and nil", 39 | first: nil, 40 | second: nil, 41 | expected: pair.Pair[any, any]{First: nil, Second: nil}, 42 | }, 43 | } 44 | 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | result := pair.New(tt.first, tt.second) 48 | if !reflect.DeepEqual(result, tt.expected) { 49 | t.Errorf("New() = %v, want %v", result, tt.expected) 50 | } 51 | }) 52 | } 53 | }) 54 | 55 | t.Run("TypeSafety", func(t *testing.T) { 56 | intStringPair := pair.New(10, "ten") 57 | if intStringPair.First != 10 || intStringPair.Second != "ten" { 58 | t.Errorf("Type safety failed for int-string pair") 59 | } 60 | 61 | floatBoolPair := pair.New(3.14, true) 62 | if floatBoolPair.First != 3.14 || floatBoolPair.Second != true { 63 | t.Errorf("Type safety failed for float-bool pair") 64 | } 65 | }) 66 | 67 | t.Run("ZeroValues", func(t *testing.T) { 68 | zeroPair := pair.New[int, string](0, "") 69 | if zeroPair.First != 0 || zeroPair.Second != "" { 70 | t.Errorf("Zero value test failed: got (%v, %v), want (0, '')", zeroPair.First, zeroPair.Second) 71 | } 72 | }) 73 | 74 | t.Run("Mutability", func(t *testing.T) { 75 | p := pair.New(1, "one") 76 | p.First = 2 77 | p.Second = "two" 78 | if p.First != 2 || p.Second != "two" { 79 | t.Errorf("Mutability test failed: got (%v, %v), want (2, 'two')", p.First, p.Second) 80 | } 81 | }) 82 | 83 | t.Run("DifferentTypes", func(t *testing.T) { 84 | type custom struct { 85 | value int 86 | } 87 | p := pair.New([]int{1, 2, 3}, custom{value: 42}) 88 | if !reflect.DeepEqual(p.First, []int{1, 2, 3}) || p.Second.value != 42 { 89 | t.Errorf("Different types test failed: got (%v, %v), want ([1 2 3], {42})", p.First, p.Second) 90 | } 91 | }) 92 | } 93 | 94 | func TestString(t *testing.T) { 95 | var tests = []struct { 96 | name string 97 | p pair.Pair[any, any] 98 | expected string 99 | }{ 100 | { 101 | name: "IntString", 102 | p: pair.New[any, any](42, "hello"), 103 | expected: "(42,hello)", 104 | }, 105 | { 106 | name: "FloatBool", 107 | p: pair.New[any, any](3.14, "world"), 108 | expected: "(3.14,world)", 109 | }, 110 | { 111 | name: "StringNil", 112 | p: pair.New[any, any](42, ""), 113 | expected: "(42,)", 114 | }, 115 | { 116 | name: "NilNil", 117 | p: pair.New[any, any](nil, nil), 118 | expected: "(,)", 119 | }, 120 | } 121 | for _, tt := range tests { 122 | t.Run(tt.name, func(t *testing.T) { 123 | result := tt.p.String() 124 | if result != tt.expected { 125 | t.Errorf("String() = %v, want %v", result, tt.expected) 126 | } 127 | }) 128 | } 129 | } 130 | 131 | func TestCompare(t *testing.T) { 132 | var tests = []struct { 133 | name string 134 | p1 pair.Pair[int, string] 135 | p2 pair.Pair[int, string] 136 | expected int 137 | }{ 138 | { 139 | name: "Equal", 140 | p1: pair.New(1, "one"), 141 | p2: pair.New(1, "one"), 142 | expected: 0, 143 | }, 144 | { 145 | name: "FirstLess", 146 | p1: pair.New(1, "one"), 147 | p2: pair.New(2, "two"), 148 | expected: -1, 149 | }, 150 | { 151 | name: "FirstGreater", 152 | p1: pair.New(2, "two"), 153 | p2: pair.New(1, "one"), 154 | expected: 1, 155 | }, 156 | { 157 | name: "SecondLess", 158 | p1: pair.New(1, "one"), 159 | p2: pair.New(1, "two"), 160 | expected: -1, 161 | }, 162 | { 163 | name: "SecondGreater", 164 | p1: pair.New(1, "two"), 165 | p2: pair.New(1, "one"), 166 | expected: 1, 167 | }, 168 | } 169 | for _, tt := range tests { 170 | t.Run(tt.name, func(t *testing.T) { 171 | result := pair.Compare(tt.p1, tt.p2) 172 | if result != tt.expected { 173 | t.Errorf("Compare() = %v, want %v", result, tt.expected) 174 | } 175 | }) 176 | } 177 | } 178 | 179 | func TestCompareFirst(t *testing.T) { 180 | var tests = []struct { 181 | name string 182 | p1 pair.Pair[int, string] 183 | p2 pair.Pair[int, string] 184 | expected int 185 | }{ 186 | { 187 | name: "Equal", 188 | p1: pair.New(1, "one"), 189 | p2: pair.New(1, "one"), 190 | expected: 0, 191 | }, 192 | { 193 | name: "FirstLess", 194 | p1: pair.New(1, "one"), 195 | p2: pair.New(2, "two"), 196 | expected: -1, 197 | }, 198 | { 199 | name: "FirstGreater", 200 | p1: pair.New(2, "two"), 201 | p2: pair.New(1, "one"), 202 | expected: 1, 203 | }, 204 | { 205 | name: "SecondLess", 206 | p1: pair.New(1, "one"), 207 | p2: pair.New(1, "two"), 208 | expected: 0, 209 | }, 210 | { 211 | name: "SecondGreater", 212 | p1: pair.New(1, "two"), 213 | p2: pair.New(1, "one"), 214 | expected: 0, 215 | }, 216 | } 217 | for _, tt := range tests { 218 | t.Run(tt.name, func(t *testing.T) { 219 | result := pair.CompareFirst(tt.p1, tt.p2) 220 | if result != tt.expected { 221 | t.Errorf("CompareFirst() = %v, want %v", result, tt.expected) 222 | } 223 | }) 224 | } 225 | } 226 | 227 | func TestCompareSecond(t *testing.T) { 228 | var tests = []struct { 229 | name string 230 | p1 pair.Pair[int, string] 231 | p2 pair.Pair[int, string] 232 | expected int 233 | }{ 234 | { 235 | name: "Equal", 236 | p1: pair.New(1, "one"), 237 | p2: pair.New(1, "one"), 238 | expected: 0, 239 | }, 240 | { 241 | name: "FirstLess", 242 | p1: pair.New(1, "one"), 243 | p2: pair.New(2, "one"), 244 | expected: 0, 245 | }, 246 | { 247 | name: "FirstGreater", 248 | p1: pair.New(2, "one"), 249 | p2: pair.New(1, "one"), 250 | expected: 0, 251 | }, 252 | { 253 | name: "SecondLess", 254 | p1: pair.New(1, "one"), 255 | p2: pair.New(1, "two"), 256 | expected: -1, 257 | }, 258 | { 259 | name: "SecondGreater", 260 | p1: pair.New(1, "two"), 261 | p2: pair.New(1, "one"), 262 | expected: 1, 263 | }, 264 | } 265 | for _, tt := range tests { 266 | t.Run(tt.name, func(t *testing.T) { 267 | result := pair.CompareSecond(tt.p1, tt.p2) 268 | if result != tt.expected { 269 | t.Errorf("CompareSecond() = %v, want %v", result, tt.expected) 270 | } 271 | }) 272 | } 273 | } 274 | 275 | func ExampleNew() { 276 | p := pair.New(10, "ten") 277 | fmt.Printf("First: %v, Second: %v\n", p.First, p.Second) 278 | // Output: First: 10, Second: ten 279 | } 280 | -------------------------------------------------------------------------------- /container/tree/tree.go: -------------------------------------------------------------------------------- 1 | // Package tree provides functionality for working with tree-like data structures. 2 | package tree 3 | 4 | import ( 5 | "bytes" 6 | "container/list" 7 | "encoding/binary" 8 | "fmt" 9 | "io" 10 | 11 | "github.com/gopherd/core/container/pair" 12 | "github.com/gopherd/core/op" 13 | ) 14 | 15 | // Node represents a generic printable node in a tree structure. 16 | type Node[T comparable] interface { 17 | // String returns the node's information as a string. 18 | String() string 19 | 20 | // Parent returns the parent node or zero value if it's the root. 21 | Parent() T 22 | 23 | // NumChild returns the number of child nodes. 24 | NumChild() int 25 | 26 | // GetChildByIndex returns the child node at the given index. 27 | // It returns the zero value of T if the index is out of range. 28 | GetChildByIndex(i int) T 29 | } 30 | 31 | // NodeMarshaler extends Node with marshaling capability. 32 | type NodeMarshaler[T comparable] interface { 33 | Node[T] 34 | 35 | // Marshal serializes the node into a byte slice. 36 | Marshal() ([]byte, error) 37 | } 38 | 39 | // encodeNode writes a node's data to the provided writer. 40 | func encodeNode[T comparable](w io.Writer, id, parent uint32, node NodeMarshaler[T]) error { 41 | content, err := node.Marshal() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | // Write header: {size: uint32, id: uint32, parent: uint32} 47 | if err := binary.Write(w, binary.LittleEndian, uint32(len(content))); err != nil { 48 | return err 49 | } 50 | if err := binary.Write(w, binary.LittleEndian, id); err != nil { 51 | return err 52 | } 53 | if err := binary.Write(w, binary.LittleEndian, parent); err != nil { 54 | return err 55 | } 56 | 57 | // Write content 58 | _, err = w.Write(content) 59 | return err 60 | } 61 | 62 | // Marshal serializes a tree structure into a byte slice. 63 | func Marshal[T comparable](root NodeMarshaler[T]) ([]byte, error) { 64 | if root == nil { 65 | return nil, nil 66 | } 67 | 68 | var buf bytes.Buffer 69 | var id uint32 70 | openSet := list.New() 71 | openSet.PushBack(pair.New(uint32(0), root)) 72 | 73 | for openSet.Len() > 0 { 74 | front := openSet.Front() 75 | p := front.Value.(pair.Pair[uint32, NodeMarshaler[T]]) 76 | openSet.Remove(front) 77 | 78 | id++ 79 | if err := encodeNode(&buf, id, p.First, p.Second); err != nil { 80 | return nil, err 81 | } 82 | 83 | for i, n := 0, p.Second.NumChild(); i < n; i++ { 84 | child, ok := any(p.Second.GetChildByIndex(i)).(NodeMarshaler[T]) 85 | if ok { 86 | openSet.PushBack(pair.New(id, child)) 87 | } 88 | } 89 | } 90 | 91 | return buf.Bytes(), nil 92 | } 93 | 94 | // Options represents the configuration for stringifying a Node. 95 | type Options struct { 96 | Prefix string 97 | Parent string // Default "│ " 98 | Space string // Default " " 99 | Branch string // Default "├──" 100 | LastBranch string // Default "└──" 101 | } 102 | 103 | var defaultOptions = &Options{ 104 | Parent: "│ ", 105 | Space: " ", 106 | Branch: "├── ", 107 | LastBranch: "└── ", 108 | } 109 | 110 | // Fix ensures all options have valid values, using defaults where necessary. 111 | func (options *Options) Fix() { 112 | op.SetDefault(&options.Parent, defaultOptions.Parent) 113 | if options.Branch == "" { 114 | options.Branch = defaultOptions.Branch 115 | op.SetDefault(&options.LastBranch, defaultOptions.LastBranch) 116 | } else if options.LastBranch == "" { 117 | options.LastBranch = options.Branch 118 | } 119 | op.SetDefault(&options.Space, defaultOptions.Space) 120 | } 121 | 122 | // Stringify converts a node to a string representation. 123 | func Stringify[T comparable](node Node[T], options *Options) string { 124 | if options == nil { 125 | options = defaultOptions 126 | } else { 127 | options.Fix() 128 | } 129 | 130 | if stringer, ok := node.(interface { 131 | Stringify(*Options) string 132 | }); ok { 133 | return stringer.Stringify(options) 134 | } 135 | 136 | var buf, stack bytes.Buffer 137 | if options.Prefix != "" { 138 | stack.WriteString(options.Prefix) 139 | } 140 | recursivelyPrintNode[T](node, &buf, &stack, "", 0, false, options) 141 | return buf.String() 142 | } 143 | 144 | // recursivelyPrintNode prints a node and its children recursively. 145 | func recursivelyPrintNode[T comparable]( 146 | x any, 147 | w io.Writer, 148 | stack *bytes.Buffer, 149 | prefix string, 150 | depth int, 151 | isLast bool, 152 | options *Options, 153 | ) { 154 | node, ok := x.(Node[T]) 155 | if !ok { 156 | return 157 | } 158 | 159 | nprefix := stack.Len() 160 | value := node.String() 161 | parent := node.Parent() 162 | fmt.Fprintf(w, "%s%s%s\n", stack.String(), prefix, value) 163 | 164 | var zero T 165 | if parent != zero { 166 | if isLast { 167 | stack.WriteString(options.Space) 168 | } else { 169 | stack.WriteString(options.Parent) 170 | } 171 | } 172 | 173 | n := node.NumChild() 174 | for i := 0; i < n; i++ { 175 | isLast := i+1 == n 176 | appended := op.If(isLast, options.LastBranch, options.Branch) 177 | child := node.GetChildByIndex(i) 178 | if child == zero { 179 | continue 180 | } 181 | recursivelyPrintNode[T](child, w, stack, appended, depth+1, isLast, options) 182 | } 183 | 184 | if nprefix != stack.Len() { 185 | stack.Truncate(nprefix) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /container/tree/tree_test.go: -------------------------------------------------------------------------------- 1 | package tree_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/gopherd/core/container/tree" 10 | ) 11 | 12 | // MockNode implements the Node and NodeMarshaler interfaces for testing 13 | type MockNode struct { 14 | id int 15 | parent *MockNode 16 | children []*MockNode 17 | data string 18 | } 19 | 20 | func (n *MockNode) String() string { return n.data } 21 | func (n *MockNode) Parent() *MockNode { return n.parent } 22 | func (n *MockNode) NumChild() int { return len(n.children) } 23 | func (n *MockNode) GetChildByIndex(i int) *MockNode { 24 | if i < 0 || i >= len(n.children) { 25 | return nil 26 | } 27 | return n.children[i] 28 | } 29 | func (n *MockNode) Marshal() ([]byte, error) { return []byte(n.data), nil } 30 | 31 | func TestStringify(t *testing.T) { 32 | root := &MockNode{id: 1, data: "Root"} 33 | child1 := &MockNode{id: 2, parent: root, data: "Child 1"} 34 | child2 := &MockNode{id: 3, parent: root, data: "Child 2"} 35 | grandchild := &MockNode{id: 4, parent: child1, data: "Grandchild"} 36 | 37 | root.children = []*MockNode{child1, child2} 38 | child1.children = []*MockNode{grandchild} 39 | 40 | tests := []struct { 41 | name string 42 | node tree.Node[*MockNode] 43 | options *tree.Options 44 | expected string 45 | }{ 46 | { 47 | name: "Default options", 48 | node: root, 49 | options: nil, 50 | expected: "Root\n├── Child 1\n│ └── Grandchild\n└── Child 2\n", 51 | }, 52 | { 53 | name: "Custom options", 54 | node: root, 55 | options: &tree.Options{ 56 | Prefix: " ", 57 | Parent: "| ", 58 | Space: " ", 59 | Branch: "+- ", 60 | LastBranch: "\\- ", 61 | }, 62 | expected: " Root\n +- Child 1\n | \\- Grandchild\n \\- Child 2\n", 63 | }, 64 | } 65 | 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | result := tree.Stringify(tt.node, tt.options) 69 | if result != tt.expected { 70 | t.Errorf("Stringify() =\n%v\nwant\n%v", result, tt.expected) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestMarshal(t *testing.T) { 77 | root := &MockNode{id: 1, data: "Root"} 78 | child := &MockNode{id: 2, parent: root, data: "Child"} 79 | root.children = []*MockNode{child} 80 | 81 | data, err := tree.Marshal[*MockNode](root) 82 | if err != nil { 83 | t.Fatalf("Marshal() error = %v", err) 84 | } 85 | 86 | // Verify the structure of the marshaled data 87 | reader := bytes.NewReader(data) 88 | 89 | // Check root node 90 | var size, id, parentID uint32 91 | if err := binary.Read(reader, binary.LittleEndian, &size); err != nil { 92 | t.Fatalf("Failed to read size: %v", err) 93 | } 94 | if err := binary.Read(reader, binary.LittleEndian, &id); err != nil { 95 | t.Fatalf("Failed to read id: %v", err) 96 | } 97 | if err := binary.Read(reader, binary.LittleEndian, &parentID); err != nil { 98 | t.Fatalf("Failed to read parentID: %v", err) 99 | } 100 | 101 | content := make([]byte, size) 102 | if _, err := reader.Read(content); err != nil { 103 | t.Fatalf("Failed to read content: %v", err) 104 | } 105 | 106 | if id != 1 || parentID != 0 || string(content) != "Root" { 107 | t.Errorf("Unexpected root node data: id=%d, parentID=%d, content=%s", id, parentID, content) 108 | } 109 | 110 | // Check child node 111 | if err := binary.Read(reader, binary.LittleEndian, &size); err != nil { 112 | t.Fatalf("Failed to read size: %v", err) 113 | } 114 | if err := binary.Read(reader, binary.LittleEndian, &id); err != nil { 115 | t.Fatalf("Failed to read id: %v", err) 116 | } 117 | if err := binary.Read(reader, binary.LittleEndian, &parentID); err != nil { 118 | t.Fatalf("Failed to read parentID: %v", err) 119 | } 120 | 121 | content = make([]byte, size) 122 | if _, err := reader.Read(content); err != nil { 123 | t.Fatalf("Failed to read content: %v", err) 124 | } 125 | 126 | if id != 2 || parentID != 1 || string(content) != "Child" { 127 | t.Errorf("Unexpected child node data: id=%d, parentID=%d, content=%s", id, parentID, content) 128 | } 129 | 130 | // Ensure we've read all the data 131 | if reader.Len() != 0 { 132 | t.Errorf("Unexpected extra data in marshaled output") 133 | } 134 | } 135 | 136 | func TestMarshalNilRoot(t *testing.T) { 137 | data, err := tree.Marshal[*MockNode](nil) 138 | if err != nil { 139 | t.Fatalf("Marshal(nil) error = %v", err) 140 | } 141 | if len(data) != 0 { 142 | t.Errorf("Marshal(nil) returned non-empty data: %v", data) 143 | } 144 | } 145 | 146 | func TestOptionsFix(t *testing.T) { 147 | tests := []struct { 148 | name string 149 | input tree.Options 150 | expected tree.Options 151 | }{ 152 | { 153 | name: "Empty options", 154 | input: tree.Options{}, 155 | expected: tree.Options{Parent: "│ ", Space: " ", Branch: "├── ", LastBranch: "└── "}, 156 | }, 157 | { 158 | name: "Custom Branch without LastBranch", 159 | input: tree.Options{Branch: ">> "}, 160 | expected: tree.Options{Parent: "│ ", Space: " ", Branch: ">> ", LastBranch: ">> "}, 161 | }, 162 | { 163 | name: "Custom everything", 164 | input: tree.Options{Parent: "P ", Space: "S ", Branch: "B ", LastBranch: "L "}, 165 | expected: tree.Options{Parent: "P ", Space: "S ", Branch: "B ", LastBranch: "L "}, 166 | }, 167 | } 168 | 169 | for _, tt := range tests { 170 | t.Run(tt.name, func(t *testing.T) { 171 | options := tt.input 172 | options.Fix() 173 | if !reflect.DeepEqual(options, tt.expected) { 174 | t.Errorf("fix() = %v, want %v", options, tt.expected) 175 | } 176 | }) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /container/trie/trie.go: -------------------------------------------------------------------------------- 1 | // Package trie implements a prefix tree (trie) data structure. 2 | package trie 3 | 4 | import ( 5 | "github.com/gopherd/core/container/tree" 6 | ) 7 | 8 | // Trie represents a prefix tree data structure. 9 | type Trie struct { 10 | root *node 11 | hasEmptyString bool 12 | } 13 | 14 | // New creates and returns a new Trie. 15 | func New() *Trie { 16 | return &Trie{ 17 | root: newNode(0, nil), 18 | } 19 | } 20 | 21 | // String returns a string representation of the Trie. 22 | func (t *Trie) String() string { 23 | return t.Stringify(nil) 24 | } 25 | 26 | // Stringify formats the Trie as a string using the provided options. 27 | func (t *Trie) Stringify(options *tree.Options) string { 28 | return tree.Stringify[*node](t.root, options) 29 | } 30 | 31 | // search traverses the Trie for the given prefix and returns the last matching node, 32 | // the depth of the match, and whether the prefix was fully matched. 33 | func (t *Trie) search(prefix string) (lastMatchNode *node, depth int, match bool) { 34 | if prefix == "" { 35 | return t.root, 0, false 36 | } 37 | 38 | lastMatchNode = t.root 39 | depth = 0 40 | match = true 41 | current := t.root 42 | 43 | for i, r := range prefix { 44 | current = current.search(r) 45 | if current == nil { 46 | match = false 47 | return 48 | } 49 | lastMatchNode = current 50 | depth = i + 1 51 | } 52 | return 53 | } 54 | 55 | // Add inserts a word into the Trie. 56 | func (t *Trie) Add(word string) { 57 | if word == "" { 58 | t.hasEmptyString = true 59 | return 60 | } 61 | n, depth, _ := t.search(word) 62 | for i, r := range word { 63 | if i >= depth { 64 | n = n.add(r) 65 | } 66 | } 67 | n.tail = true 68 | } 69 | 70 | // Remove deletes a word from the Trie if it exists. 71 | // It returns true if the word was found and removed, false otherwise. 72 | func (t *Trie) Remove(word string) bool { 73 | if word == "" { 74 | if t.hasEmptyString { 75 | t.hasEmptyString = false 76 | return true 77 | } 78 | return false 79 | } 80 | n, _, match := t.search(word) 81 | if match && n.tail { 82 | n.tail = false 83 | for n.parent != nil && !n.tail && len(n.children) == 0 { 84 | n.parent.remove(n.value) 85 | n = n.parent 86 | } 87 | return true 88 | } 89 | return false 90 | } 91 | 92 | // Has checks if the Trie contains the exact word. 93 | func (t *Trie) Has(word string) bool { 94 | if word == "" { 95 | return t.hasEmptyString 96 | } 97 | n, _, match := t.search(word) 98 | return match && n.tail 99 | } 100 | 101 | // HasPrefix checks if the Trie contains any word with the given prefix. 102 | func (t *Trie) HasPrefix(prefix string) bool { 103 | if prefix == "" { 104 | return true 105 | } 106 | _, _, match := t.search(prefix) 107 | return match 108 | } 109 | 110 | // Search retrieves words in the Trie that have the specified prefix. 111 | // It returns up to 'limit' number of words. If limit is 0, it returns all matching words. 112 | func (t *Trie) Search(prefix string, limit int) []string { 113 | return t.SearchAppend(nil, prefix, limit) 114 | } 115 | 116 | // SearchAppend is similar to Search but appends the results to the provided slice. 117 | // It returns the updated slice containing the matching words. 118 | func (t *Trie) SearchAppend(dst []string, prefix string, limit int) []string { 119 | if limit == 0 { 120 | limit = -1 121 | } else if limit > 0 { 122 | limit = limit - len(dst) 123 | if limit <= 0 { 124 | return dst 125 | } 126 | } 127 | n, depth, _ := t.search(prefix) 128 | if depth != len(prefix) { 129 | return dst 130 | } 131 | var buf []rune 132 | return n.words(dst, limit, append(buf, []rune(prefix)[:depth]...)) 133 | } 134 | 135 | // node represents a node in the Trie. 136 | type node struct { 137 | value rune 138 | parent *node 139 | children []*node 140 | tail bool 141 | } 142 | 143 | // newNode creates a new node with the given rune value and parent. 144 | func newNode(r rune, parent *node) *node { 145 | return &node{ 146 | value: r, 147 | parent: parent, 148 | children: make([]*node, 0, 2), 149 | } 150 | } 151 | 152 | // String implements the container.Node String method. 153 | func (n *node) String() string { 154 | if n.parent == nil { 155 | return "." 156 | } 157 | return string(n.value) 158 | } 159 | 160 | // Parent implements the container.Node Parent method. 161 | func (n *node) Parent() *node { 162 | return n.parent 163 | } 164 | 165 | // NumChild implements the container.Node NumChild method. 166 | func (n *node) NumChild() int { 167 | return len(n.children) 168 | } 169 | 170 | // GetChildByIndex implements the container.Node GetChildByIndex method. 171 | func (n *node) GetChildByIndex(i int) *node { 172 | return n.children[i] 173 | } 174 | 175 | // indexof returns the index where a child node with the given rune should be inserted. 176 | func (n *node) indexof(r rune) int { 177 | left, right := 0, len(n.children) 178 | for left < right { 179 | mid := int(uint(left+right) >> 1) 180 | if n.children[mid].value < r { 181 | left = mid + 1 182 | } else { 183 | right = mid 184 | } 185 | } 186 | return left 187 | } 188 | 189 | // add inserts a new child node with the given rune value and returns it. 190 | func (n *node) add(r rune) *node { 191 | i := n.indexof(r) 192 | if i < len(n.children) && n.children[i].value == r { 193 | return n.children[i] 194 | } 195 | child := newNode(r, n) 196 | n.children = append(n.children, nil) 197 | copy(n.children[i+1:], n.children[i:]) 198 | n.children[i] = child 199 | return child 200 | } 201 | 202 | // remove deletes the child node with the given rune value. 203 | func (n *node) remove(r rune) { 204 | i := n.indexof(r) 205 | if i == len(n.children) || n.children[i].value != r { 206 | return 207 | } 208 | n.children = append(n.children[:i], n.children[i+1:]...) 209 | } 210 | 211 | // search finds and returns the child node with the given rune value. 212 | func (n *node) search(r rune) *node { 213 | i := n.indexof(r) 214 | if i < len(n.children) && n.children[i].value == r { 215 | return n.children[i] 216 | } 217 | return nil 218 | } 219 | 220 | // words recursively collects words from the current node and its children. 221 | func (n *node) words(dst []string, limit int, buf []rune) []string { 222 | if n.tail { 223 | dst = append(dst, string(buf)) 224 | if limit > 0 && len(dst) == limit { 225 | return dst 226 | } 227 | } 228 | for _, child := range n.children { 229 | dst = child.words(dst, limit, append(buf, child.value)) 230 | if limit > 0 && len(dst) >= limit { 231 | break 232 | } 233 | } 234 | return dst 235 | } 236 | -------------------------------------------------------------------------------- /container/trie/trie_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/gopherd/core/container/tree" 9 | ) 10 | 11 | func TestNew(t *testing.T) { 12 | tr := New() 13 | if tr == nil { 14 | t.Fatal("New() returned nil") 15 | } 16 | } 17 | 18 | func TestAdd(t *testing.T) { 19 | tr := New() 20 | words := []string{"apple", "app", "application", "banana", ""} 21 | for _, word := range words { 22 | tr.Add(word) 23 | } 24 | for _, word := range words { 25 | if !tr.Has(word) { 26 | t.Errorf("Add() failed for word: %s", word) 27 | } 28 | } 29 | } 30 | 31 | func TestRemove(t *testing.T) { 32 | tr := New() 33 | words := []string{"apple", "app", "application", "banana", ""} 34 | for _, word := range words { 35 | tr.Add(word) 36 | } 37 | 38 | // Test removing existing words 39 | for _, word := range words { 40 | if !tr.Remove(word) { 41 | t.Errorf("Remove() failed for existing word: %s", word) 42 | } 43 | if tr.Has(word) { 44 | t.Errorf("Word still exists after removal: %s", word) 45 | } 46 | } 47 | 48 | // Test removing non-existing word 49 | if tr.Remove("nonexistent") { 50 | t.Error("Remove() returned true for non-existing word") 51 | } 52 | 53 | // Test removing empty string 54 | tr.Add("") 55 | if !tr.Remove("") { 56 | t.Error("Remove() failed for empty string") 57 | } 58 | } 59 | 60 | func TestHas(t *testing.T) { 61 | tr := New() 62 | words := []string{"apple", "app", "application", "banana", ""} 63 | for _, word := range words { 64 | tr.Add(word) 65 | } 66 | 67 | // Test existing words 68 | for _, word := range words { 69 | if !tr.Has(word) { 70 | t.Errorf("Has() returned false for existing word: %s", word) 71 | } 72 | } 73 | 74 | // Test non-existing words 75 | nonExisting := []string{"ap", "appl", "banan", "cherry"} 76 | for _, word := range nonExisting { 77 | if tr.Has(word) { 78 | t.Errorf("Has() returned true for non-existing word: %s", word) 79 | } 80 | } 81 | } 82 | 83 | func TestHasPrefix(t *testing.T) { 84 | tr := New() 85 | words := []string{"apple", "app", "application", "banana", ""} 86 | for _, word := range words { 87 | tr.Add(word) 88 | } 89 | 90 | // Test valid prefixes 91 | validPrefixes := []string{"", "a", "ap", "app", "appl", "ban", "banana"} 92 | for _, prefix := range validPrefixes { 93 | if !tr.HasPrefix(prefix) { 94 | t.Errorf("HasPrefix() returned false for valid prefix: %s", prefix) 95 | } 96 | } 97 | 98 | // Test invalid prefixes 99 | invalidPrefixes := []string{"bx", "c", "cherry"} 100 | for _, prefix := range invalidPrefixes { 101 | if tr.HasPrefix(prefix) { 102 | t.Errorf("HasPrefix() returned true for invalid prefix: %s", prefix) 103 | } 104 | } 105 | } 106 | 107 | func TestSearch(t *testing.T) { 108 | tr := New() 109 | words := []string{"apple", "app", "application", "banana", "ban", "bandana"} 110 | for _, word := range words { 111 | tr.Add(word) 112 | } 113 | 114 | testCases := []struct { 115 | prefix string 116 | limit int 117 | expect []string 118 | }{ 119 | {"app", 0, []string{"app", "apple", "application"}}, 120 | {"app", 2, []string{"app", "apple"}}, 121 | {"ban", 0, []string{"ban", "banana", "bandana"}}, 122 | {"ban", 1, []string{"ban"}}, 123 | {"c", 0, []string{}}, 124 | {"", 0, []string{"app", "apple", "application", "ban", "banana", "bandana"}}, 125 | {"", 3, []string{"app", "apple", "application"}}, 126 | } 127 | 128 | for _, tc := range testCases { 129 | t.Run(fmt.Sprintf("prefix=%s,limit=%d", tc.prefix, tc.limit), func(t *testing.T) { 130 | result := tr.Search(tc.prefix, tc.limit) 131 | if !equal(result, tc.expect) { 132 | t.Errorf("Search(%q, %d) = %v; want %v", tc.prefix, tc.limit, result, tc.expect) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func TestSearchAppend(t *testing.T) { 139 | tr := New() 140 | words := []string{"apple", "app", "application", "banana", "ban", "bandana"} 141 | for _, word := range words { 142 | tr.Add(word) 143 | } 144 | 145 | testCases := []struct { 146 | prefix string 147 | limit int 148 | initial []string 149 | expected []string 150 | }{ 151 | {"app", 0, []string{"existing"}, []string{"existing", "app", "apple", "application"}}, 152 | {"ban", 2, []string{"prefix"}, []string{"prefix", "ban", "banana"}}, 153 | {"c", 0, []string{"no match"}, []string{"no match"}}, 154 | } 155 | 156 | for _, tc := range testCases { 157 | t.Run(fmt.Sprintf("prefix=%s,limit=%d", tc.prefix, tc.limit), func(t *testing.T) { 158 | result := tr.SearchAppend(tc.initial, tc.prefix, tc.limit) 159 | if !equal(result, tc.expected) { 160 | t.Errorf("SearchAppend(%v, %q, %d) = %v; want %v", tc.initial, tc.prefix, tc.limit, result, tc.expected) 161 | } 162 | }) 163 | } 164 | } 165 | 166 | func TestString(t *testing.T) { 167 | tr := New() 168 | words := []string{"app", "apple", "banana"} 169 | for _, word := range words { 170 | tr.Add(word) 171 | } 172 | 173 | result := tr.String() 174 | if result == "" { 175 | t.Error("String() returned empty string") 176 | } 177 | } 178 | 179 | func TestStringify(t *testing.T) { 180 | tr := New() 181 | words := []string{"app", "apple", "banana"} 182 | for _, word := range words { 183 | tr.Add(word) 184 | } 185 | 186 | options := &tree.Options{ 187 | Prefix: " ", 188 | } 189 | result := tr.Stringify(options) 190 | if result == "" { 191 | t.Error("Stringify() returned empty string") 192 | } 193 | 194 | // Test with nil options 195 | nilResult := tr.Stringify(nil) 196 | if nilResult == "" { 197 | t.Error("Stringify(nil) returned empty string") 198 | } 199 | } 200 | 201 | // Helper function to compare two string slices 202 | func equal(a, b []string) bool { 203 | if len(a) != len(b) { 204 | return false 205 | } 206 | for i, v := range a { 207 | if v != b[i] { 208 | return false 209 | } 210 | } 211 | return true 212 | } 213 | 214 | func TestRemoveEmptyString(t *testing.T) { 215 | tr := New() 216 | 217 | if tr.Remove("") { 218 | t.Error("Remove() returned true for non-existing empty string") 219 | } 220 | 221 | tr.Add("") 222 | if !tr.Remove("") { 223 | t.Error("Remove() returned false for existing empty string") 224 | } 225 | 226 | if tr.Remove("") { 227 | t.Error("Remove() returned true for already removed empty string") 228 | } 229 | } 230 | 231 | func TestSearchAppendEdgeCases(t *testing.T) { 232 | tr := New() 233 | words := []string{"apple", "app", "application"} 234 | for _, word := range words { 235 | tr.Add(word) 236 | } 237 | 238 | result := tr.SearchAppend([]string{"existing"}, "app", 0) 239 | expected := []string{"existing", "app", "apple", "application"} 240 | if !reflect.DeepEqual(result, expected) { 241 | t.Errorf("SearchAppend with limit 0 failed. Got %v, want %v", result, expected) 242 | } 243 | 244 | result = tr.SearchAppend([]string{"existing", "word"}, "app", 1) 245 | expected = []string{"existing", "word"} 246 | if !reflect.DeepEqual(result, expected) { 247 | t.Errorf("SearchAppend with limit <= len(dst) failed. Got %v, want %v", result, expected) 248 | } 249 | 250 | result = tr.SearchAppend([]string{"existing"}, "banana", 5) 251 | expected = []string{"existing"} 252 | if !reflect.DeepEqual(result, expected) { 253 | t.Errorf("SearchAppend with non-matching prefix failed. Got %v, want %v", result, expected) 254 | } 255 | } 256 | 257 | func TestNodeAddExistingRune(t *testing.T) { 258 | tr := New() 259 | tr.Add("test") 260 | 261 | root := tr.root 262 | child := root.add('t') 263 | 264 | sameChild := root.add('t') 265 | 266 | if child != sameChild { 267 | t.Error("Adding existing rune should return the same node") 268 | } 269 | } 270 | 271 | func TestRemoveNonExistentRune(t *testing.T) { 272 | tr := New() 273 | tr.Add("test") 274 | 275 | root := tr.root 276 | tNode := root.search('t') 277 | 278 | if tNode == nil { 279 | t.Fatal("Failed to get 't' node") 280 | } 281 | 282 | tNode.remove('x') 283 | if tNode.NumChild() != 1 || tNode.search('e') == nil { 284 | t.Error("Removing non-existent rune should not affect existing children") 285 | } 286 | 287 | tNode.remove('s') 288 | if tNode.NumChild() != 1 || tNode.search('e') == nil { 289 | t.Error("Removing non-child rune should not affect existing children") 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /encoding/encoding.go: -------------------------------------------------------------------------------- 1 | // Package encoding provides interfaces, utilities, and common functions for 2 | // encoding and decoding data in various formats. 3 | // 4 | // This package defines common types for encoders and decoders, as well as 5 | // utility functions that can be used across different encoding schemes. 6 | // It serves as a foundation for building more specific encoding/decoding 7 | // functionalities while providing a consistent interface. 8 | package encoding 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "fmt" 14 | ) 15 | 16 | // Encoder is a function type that encodes a value into bytes. 17 | type Encoder func(any) ([]byte, error) 18 | 19 | // Decoder is a function type that decodes bytes into a provided value. 20 | type Decoder func([]byte, any) error 21 | 22 | // Transform decodes the input data using the provided decoder, 23 | // then re-encodes it using the provided encoder. 24 | // It returns the encoded bytes and any error encountered during the process. 25 | // 26 | // The decoder should populate the provided value with the decoded data. 27 | // The encoder should take the decoded value and produce the encoded bytes. 28 | // 29 | // If an error occurs during decoding or encoding, Transform returns nil for the bytes 30 | // and the error describing the failure. 31 | func Transform(data []byte, decoder Decoder, encoder Encoder) ([]byte, error) { 32 | var v any 33 | if err := decoder(data, &v); err != nil { 34 | return nil, fmt.Errorf("decoding error: %w", err) 35 | } 36 | 37 | encodedValue, err := encoder(v) 38 | if err != nil { 39 | return nil, fmt.Errorf("encoding error: %w", err) 40 | } 41 | 42 | return encodedValue, nil 43 | } 44 | 45 | // GetPosition returns the line and column number of the given offset in the data. 46 | func GetPosition(data []byte, offset int) (line, column int) { 47 | line = 1 48 | column = 1 49 | 50 | for i := 0; i < offset && i < len(data); i++ { 51 | if data[i] == '\n' { 52 | line++ 53 | column = 1 54 | } else if data[i] == '\r' { 55 | if i+1 < len(data) && data[i+1] == '\n' { 56 | continue 57 | } 58 | line++ 59 | column = 1 60 | } else { 61 | column++ 62 | } 63 | } 64 | 65 | return line, column 66 | } 67 | 68 | type SourceError struct { 69 | Filename string 70 | Line int 71 | Column int 72 | Offset int 73 | Context string 74 | Err error 75 | } 76 | 77 | func (e *SourceError) Error() string { 78 | return fmt.Sprintf("%s:%d:%d(%s): %v", e.Filename, e.Line, e.Column, e.Context, e.Err) 79 | } 80 | 81 | func (e *SourceError) Unwrap() error { 82 | return e.Err 83 | } 84 | 85 | func GetJSONSourceError(filename string, data []byte, err error) error { 86 | if err == nil { 87 | return err 88 | } 89 | 90 | var offset int 91 | switch e := err.(type) { 92 | case *json.SyntaxError: 93 | offset = int(e.Offset) 94 | case *json.UnmarshalTypeError: 95 | offset = int(e.Offset) 96 | default: 97 | return err 98 | } 99 | if offset <= 0 { 100 | return err 101 | } 102 | 103 | const maxContext = 64 104 | line, column := GetPosition(data, offset) 105 | begin := bytes.LastIndexByte(data[:offset], '\n') + 1 106 | context := string(data[begin:offset]) 107 | if offset-begin > maxContext { 108 | begin = offset - maxContext 109 | context = "..." + string(data[begin:offset]) 110 | } 111 | return &SourceError{ 112 | Filename: filename, 113 | Line: line, 114 | Column: column, 115 | Offset: offset, 116 | Context: context, 117 | Err: err, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /enum/enum.go: -------------------------------------------------------------------------------- 1 | // Package enum provides a way to register enum descriptors and lookup them by name. 2 | package enum 3 | 4 | import ( 5 | "errors" 6 | "sync" 7 | ) 8 | 9 | // Descriptor is a struct that describes an enum type. 10 | type Descriptor struct { 11 | Name string `json:"name"` 12 | Description string `json:"description"` 13 | Members []MemberDescriptor `json:"members"` 14 | } 15 | 16 | // MemberDescriptor is a struct that describes an enum member. 17 | type MemberDescriptor struct { 18 | Name string `json:"name"` 19 | Value any `json:"value"` 20 | Description string `json:"description"` 21 | } 22 | 23 | // Registry is a struct that holds a map of enum descriptors. 24 | type Registry struct { 25 | descriptorsMu sync.RWMutex 26 | descriptors map[string]*Descriptor 27 | } 28 | 29 | // Register registers an enum descriptor. 30 | func (r *Registry) Register(descriptor *Descriptor) error { 31 | r.descriptorsMu.Lock() 32 | defer r.descriptorsMu.Unlock() 33 | if r.descriptors == nil { 34 | r.descriptors = make(map[string]*Descriptor) 35 | } 36 | if _, dup := r.descriptors[descriptor.Name]; dup { 37 | return errors.New("enums: Register called twice for descriptor " + descriptor.Name) 38 | } 39 | r.descriptors[descriptor.Name] = descriptor 40 | return nil 41 | } 42 | 43 | // Lookup looks up an enum descriptor by name. 44 | func (r *Registry) Lookup(name string) *Descriptor { 45 | r.descriptorsMu.RLock() 46 | defer r.descriptorsMu.RUnlock() 47 | if r.descriptors == nil { 48 | return nil 49 | } 50 | return r.descriptors[name] 51 | } 52 | -------------------------------------------------------------------------------- /enum/enum_test.go: -------------------------------------------------------------------------------- 1 | package enum_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopherd/core/enum" 7 | ) 8 | 9 | func TestRegistry(t *testing.T) { 10 | var r enum.Registry 11 | if r.Lookup("Color") != nil { 12 | t.Errorf("LookupDescriptor failed: Color found") 13 | } 14 | if err := r.Register(&enum.Descriptor{ 15 | Name: "Color", 16 | Description: "Color enum", 17 | Members: []enum.MemberDescriptor{ 18 | {Name: "Red", Value: 0, Description: "Red color"}, 19 | {Name: "Green", Value: 1, Description: "Green color"}, 20 | {Name: "Blue", Value: 2, Description: "Blue color"}, 21 | }, 22 | }); err != nil { 23 | t.Errorf("RegisterDescriptor failed: %v", err) 24 | } 25 | if err := r.Register(&enum.Descriptor{ 26 | Name: "Shape", 27 | Description: "Shape enum", 28 | Members: []enum.MemberDescriptor{ 29 | {Name: "Circle", Value: 0, Description: "Circle shape"}, 30 | {Name: "Square", Value: 1, Description: "Square shape"}, 31 | {Name: "Triangle", Value: 2, Description: "Triangle shape"}, 32 | }, 33 | }); err != nil { 34 | t.Errorf("RegisterDescriptor failed: %v", err) 35 | } 36 | if err := r.Register(&enum.Descriptor{ 37 | Name: "Color", 38 | Description: "Color enum", 39 | Members: []enum.MemberDescriptor{ 40 | {Name: "Red", Value: 0, Description: "Red color"}, 41 | {Name: "Green", Value: 1, Description: "Green color"}, 42 | {Name: "Blue", Value: 2, Description: "Blue color"}, 43 | }, 44 | }); err == nil { 45 | t.Errorf("RegisterDescriptor failed: expected error, got nil") 46 | } 47 | 48 | if d := r.Lookup("Color"); d == nil { 49 | t.Errorf("LookupDescriptor failed: Color not found") 50 | } else { 51 | if d.Name != "Color" { 52 | t.Errorf("LookupDescriptor failed: expected Color, got %s", d.Name) 53 | } 54 | if d.Description != "Color enum" { 55 | t.Errorf("LookupDescriptor failed: expected Color enum, got %s", d.Description) 56 | } 57 | if len(d.Members) != 3 { 58 | t.Errorf("LookupDescriptor failed: expected 3 members, got %d", len(d.Members)) 59 | } 60 | } 61 | 62 | if d := r.Lookup("Shape"); d == nil { 63 | t.Errorf("LookupDescriptor failed: Shape not found") 64 | } else { 65 | if d.Name != "Shape" { 66 | t.Errorf("LookupDescriptor failed: expected Shape, got %s", d.Name) 67 | } 68 | if d.Description != "Shape enum" { 69 | t.Errorf("LookupDescriptor failed: expected Shape enum, got %s", d.Description) 70 | } 71 | if len(d.Members) != 3 { 72 | t.Errorf("LookupDescriptor failed: expected 3 members, got %d", len(d.Members)) 73 | } 74 | } 75 | 76 | if d := r.Lookup("Size"); d != nil { 77 | t.Errorf("LookupDescriptor failed: Size found") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /errkit/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package errkit provides a flexible error code mechanism for Go applications. 3 | 4 | Key features: 5 | - Associate integer error codes with errors 6 | - Wrap existing errors with error codes 7 | - Add context information to errors 8 | - Check error types using error codes 9 | 10 | Error Code Mechanism Usage: 11 | 12 | 1. Define your error code type and constants: 13 | 14 | type MyErrno int 15 | 16 | const ( 17 | EUnknown MyErrno = errkit.EUnknown 18 | EOK MyErrno = errkit.EOK 19 | ENotFound MyErrno = iota + 1 20 | EInvalidInput 21 | // Define more error codes as needed 22 | ) 23 | 24 | 2. Create errors with error codes: 25 | 26 | func FindUser(id int) error { 27 | // Simulate a database lookup 28 | if id < 0 { 29 | return errkit.New(EInvalidInput, fmt.Errorf("invalid user id: %d", id)) 30 | } 31 | // User not found scenario 32 | return errkit.New(ENotFound, fmt.Errorf("user with id %d not found", id)) 33 | } 34 | 35 | 3. Add context to errors: 36 | 37 | func ProcessUser(id int) error { 38 | if err := FindUser(id); err != nil { 39 | return errkit.NewWithContext(errkit.Errno(err), err, "failed to process user") 40 | } 41 | // Process user... 42 | return nil 43 | } 44 | 45 | 4. Check error types: 46 | 47 | err := ProcessUser(42) 48 | switch errkit.Errno(err) { 49 | case ENotFound: 50 | fmt.Println("User not found") 51 | case EInvalidInput: 52 | fmt.Println("Invalid input provided") 53 | default: 54 | fmt.Println("An error occurred:", err) 55 | } 56 | 57 | 5. Use the Is function for more idiomatic error checking: 58 | 59 | if errkit.Is(err, ENotFound) { 60 | fmt.Println("User not found") 61 | } 62 | 63 | By using errkit, you can create more structured and easily identifiable errors 64 | in your Go applications, improving error handling and debugging. The error code 65 | mechanism allows for more detailed and type-safe error handling. 66 | */ 67 | package errkit 68 | -------------------------------------------------------------------------------- /errkit/errno.go: -------------------------------------------------------------------------------- 1 | package errkit 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/gopherd/core/constraints" 8 | ) 9 | 10 | // Built-in error codes 11 | const ( 12 | EUnknown = -1 // Unknown error 13 | EOK = 0 // No error 14 | ) 15 | 16 | // Error is an interface that wraps the Error and Errno method. 17 | type Error interface { 18 | error 19 | Errno() int 20 | } 21 | 22 | // errno is a struct that implements the Error interface. 23 | type errno struct { 24 | no int 25 | err error 26 | } 27 | 28 | // Errno returns the code of errno. 29 | func (err errno) Errno() int { 30 | return err.no 31 | } 32 | 33 | // Error returns the error message of errno. 34 | func (err errno) Error() string { 35 | return err.err.Error() 36 | } 37 | 38 | // Unwrap returns the wrapped error. 39 | func (err errno) Unwrap() error { 40 | return err.err 41 | } 42 | 43 | // New wraps the error with code 44 | func New[T constraints.Integer](code T, err error) Error { 45 | if err == nil { 46 | return nil 47 | } 48 | return errno{ 49 | no: int(code), 50 | err: err, 51 | } 52 | } 53 | 54 | // NewWithContext wraps an error with additional context information 55 | func NewWithContext[T constraints.Integer](code T, err error, context string) Error { 56 | if err == nil { 57 | return nil 58 | } 59 | return errno{ 60 | no: int(code), 61 | err: fmt.Errorf("%s: %w", context, err), 62 | } 63 | } 64 | 65 | // Errno finds the first error in err's chain that contains errno. 66 | // 67 | // The chain consists of err itself followed by the sequence of errors obtained by 68 | // repeatedly calling Unwrap. 69 | // 70 | // If err is nil, Errno returns EOK. 71 | // If err does not contain errno, Errno returns EUnknown. 72 | func Errno(err error) int { 73 | if err == nil { 74 | return EOK 75 | } 76 | for { 77 | if e, ok := err.(interface{ Errno() int }); ok { 78 | return e.Errno() 79 | } 80 | if err = errors.Unwrap(err); err == nil { 81 | break 82 | } 83 | } 84 | return EUnknown 85 | } 86 | -------------------------------------------------------------------------------- /errkit/errno_test.go: -------------------------------------------------------------------------------- 1 | package errkit 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestNew(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | code int 13 | err error 14 | expected Error 15 | }{ 16 | {"nil error", 1, nil, nil}, 17 | {"non-nil error", 2, errors.New("test error"), errno{no: 2, err: errors.New("test error")}}, 18 | {"zero code", 0, errors.New("zero code"), errno{no: 0, err: errors.New("zero code")}}, 19 | {"negative code", -1, errors.New("negative code"), errno{no: -1, err: errors.New("negative code")}}, 20 | } 21 | 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | result := New(tt.code, tt.err) 25 | if tt.expected == nil { 26 | if result != nil { 27 | t.Errorf("New(%d, %v) = %v, want nil", tt.code, tt.err, result) 28 | } 29 | } else { 30 | if result == nil { 31 | t.Errorf("New(%d, %v) = nil, want %v", tt.code, tt.err, tt.expected) 32 | } else if result.Errno() != tt.expected.Errno() || result.Error() != tt.expected.Error() { 33 | t.Errorf("New(%d, %v) = %v, want %v", tt.code, tt.err, result, tt.expected) 34 | } 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestNewWithContext(t *testing.T) { 41 | tests := []struct { 42 | name string 43 | code int 44 | err error 45 | context string 46 | expected Error 47 | }{ 48 | {"nil error", 1, nil, "context", nil}, 49 | {"non-nil error", 2, errors.New("test error"), "context", errno{no: 2, err: fmt.Errorf("context: %w", errors.New("test error"))}}, 50 | {"empty context", 3, errors.New("test error"), "", errno{no: 3, err: fmt.Errorf(": %w", errors.New("test error"))}}, 51 | } 52 | 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | result := NewWithContext(tt.code, tt.err, tt.context) 56 | if tt.expected == nil { 57 | if result != nil { 58 | t.Errorf("NewWithContext(%d, %v, %q) = %v, want nil", tt.code, tt.err, tt.context, result) 59 | } 60 | } else { 61 | if result == nil { 62 | t.Errorf("NewWithContext(%d, %v, %q) = nil, want %v", tt.code, tt.err, tt.context, tt.expected) 63 | } else if result.Errno() != tt.expected.Errno() || result.Error() != tt.expected.Error() { 64 | t.Errorf("NewWithContext(%d, %v, %q) = %v, want %v", tt.code, tt.err, tt.context, result, tt.expected) 65 | } 66 | } 67 | }) 68 | } 69 | } 70 | 71 | func TestErrno(t *testing.T) { 72 | tests := []struct { 73 | name string 74 | err error 75 | expected int 76 | }{ 77 | {"nil error", nil, EOK}, 78 | {"custom error", New(42, errors.New("custom error")), 42}, 79 | {"wrapped custom error", fmt.Errorf("wrapped: %w", New(42, errors.New("custom error"))), 42}, 80 | {"non-errno error", errors.New("regular error"), EUnknown}, 81 | {"deeply wrapped custom error", fmt.Errorf("outer: %w", fmt.Errorf("inner: %w", New(42, errors.New("custom error")))), 42}, 82 | } 83 | 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | result := Errno(tt.err) 87 | if result != tt.expected { 88 | t.Errorf("Errno(%v) = %d, want %d", tt.err, result, tt.expected) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func TestErrnoMethods(t *testing.T) { 95 | err := New(42, errors.New("test error")) 96 | 97 | t.Run("Errno", func(t *testing.T) { 98 | if err.Errno() != 42 { 99 | t.Errorf("err.Errno() = %d, want 42", err.Errno()) 100 | } 101 | }) 102 | 103 | t.Run("Error", func(t *testing.T) { 104 | if err.Error() != "test error" { 105 | t.Errorf("err.Error() = %q, want \"test error\"", err.Error()) 106 | } 107 | }) 108 | 109 | t.Run("Unwrap", func(t *testing.T) { 110 | unwrapped := errors.Unwrap(err) 111 | if unwrapped == nil || unwrapped.Error() != "test error" { 112 | t.Errorf("errors.Unwrap(err) = %v, want error with message \"test error\"", unwrapped) 113 | } 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /errkit/errors.go: -------------------------------------------------------------------------------- 1 | package errkit 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // exitError represents an error that causes the service to exit. 10 | type exitError struct { 11 | code int 12 | message string 13 | } 14 | 15 | func (e *exitError) Error() string { 16 | return fmt.Sprintf("(exit status %d) %s", e.code, e.message) 17 | } 18 | 19 | // NewExitError creates a new exit error with the given exit code. 20 | func NewExitError(code int, messages ...string) error { 21 | return &exitError{code: code, message: strings.Join(messages, " ")} 22 | } 23 | 24 | // ExitCode returns the exit code of the error if it is an exit error. 25 | func ExitCode(err error) (int, bool) { 26 | var exit *exitError 27 | if !errors.As(err, &exit) { 28 | return 0, false 29 | } 30 | return exit.code, true 31 | } 32 | -------------------------------------------------------------------------------- /event/event.go: -------------------------------------------------------------------------------- 1 | // Package event provides a generic event handling system. 2 | package event 3 | 4 | import ( 5 | "context" 6 | "encoding/gob" 7 | "errors" 8 | "fmt" 9 | "io" 10 | 11 | "github.com/gopherd/core/container/pair" 12 | ) 13 | 14 | // ErrUnexpectedEventType is the error returned when an unexpected event type is received. 15 | var ErrUnexpectedEventType = errors.New("unexpected event type") 16 | 17 | // ListenerID represents a unique identifier for listeners. 18 | type ListenerID int 19 | 20 | // Event is the interface that wraps the basic Typeof method. 21 | type Event[T comparable] interface { 22 | // Typeof returns the type of the event. 23 | Typeof() T 24 | } 25 | 26 | // Listener handles fired events. 27 | type Listener[T comparable] interface { 28 | // EventType returns the type of event this listener handles. 29 | EventType() T 30 | // HandleEvent processes the fired event. 31 | HandleEvent(context.Context, Event[T]) error 32 | } 33 | 34 | // Listen creates a Listener for the given event type and handler function. 35 | func Listen[H ~func(context.Context, E) error, E Event[T], T comparable](eventType T, handler H) Listener[T] { 36 | return listenerFunc[H, E, T]{eventType, handler} 37 | } 38 | 39 | type listenerFunc[H ~func(context.Context, E) error, E Event[T], T comparable] struct { 40 | eventType T 41 | handler H 42 | } 43 | 44 | // EventType implements the Listener interface. 45 | func (h listenerFunc[H, E, T]) EventType() T { 46 | return h.eventType 47 | } 48 | 49 | // HandleEvent implements the Listener interface. 50 | func (h listenerFunc[H, E, T]) HandleEvent(ctx context.Context, event Event[T]) error { 51 | if e, ok := event.(E); ok { 52 | return h.handler(ctx, e) 53 | } 54 | return fmt.Errorf("%w: got %T for type %v", ErrUnexpectedEventType, event, event.Typeof()) 55 | } 56 | 57 | // ListenerAdder adds a new listener and returns its ID. 58 | type ListenerAdder[T comparable] interface { 59 | AddListener(Listener[T]) ListenerID 60 | } 61 | 62 | // ListenerRemover removes a listener by its ID. 63 | type ListenerRemover interface { 64 | RemoveListener(ListenerID) bool 65 | } 66 | 67 | // ListenerChecker checks if a listener exists by its ID. 68 | type ListenerChecker interface { 69 | HasListener(ListenerID) bool 70 | } 71 | 72 | // Dispatcher dispatches events. 73 | type Dispatcher[T comparable] interface { 74 | DispatchEvent(context.Context, Event[T]) error 75 | } 76 | 77 | // EventSystem is the interface that manages listeners and dispatches events. 78 | type EventSystem[T comparable] interface { 79 | ListenerAdder[T] 80 | ListenerRemover 81 | ListenerChecker 82 | Dispatcher[T] 83 | } 84 | 85 | type eventSystem[T comparable] struct { 86 | nextID ListenerID 87 | ordered bool 88 | listeners map[T][]pair.Pair[ListenerID, Listener[T]] 89 | mapping map[ListenerID]pair.Pair[T, int] 90 | } 91 | 92 | func newDispatcher[T comparable](ordered bool) *eventSystem[T] { 93 | return &eventSystem[T]{ 94 | ordered: ordered, 95 | listeners: make(map[T][]pair.Pair[ListenerID, Listener[T]]), 96 | mapping: make(map[ListenerID]pair.Pair[T, int]), 97 | } 98 | } 99 | 100 | // NewEventSystem creates a new EventSystem instance. 101 | func NewEventSystem[T comparable](ordered bool) EventSystem[T] { 102 | return newDispatcher[T](ordered) 103 | } 104 | 105 | // AddListener implements the ListenerAdder interface. 106 | func (es *eventSystem[T]) AddListener(listener Listener[T]) ListenerID { 107 | es.nextID++ 108 | id := es.nextID 109 | eventType := listener.EventType() 110 | listeners := es.listeners[eventType] 111 | index := len(listeners) 112 | es.listeners[eventType] = append(listeners, pair.New(id, listener)) 113 | es.mapping[id] = pair.New(eventType, index) 114 | return id 115 | } 116 | 117 | // RemoveListener implements the ListenerRemover interface. 118 | func (es *eventSystem[T]) RemoveListener(id ListenerID) bool { 119 | index, ok := es.mapping[id] 120 | if !ok { 121 | return false 122 | } 123 | eventType := index.First 124 | listeners := es.listeners[eventType] 125 | last := len(listeners) - 1 126 | if index.Second != last { 127 | if es.ordered { 128 | copy(listeners[index.Second:last], listeners[index.Second+1:]) 129 | for i := index.Second; i < last; i++ { 130 | es.mapping[listeners[i].First] = pair.New(eventType, i) 131 | } 132 | } else { 133 | listeners[index.Second] = listeners[last] 134 | es.mapping[listeners[index.Second].First] = pair.New(eventType, index.Second) 135 | } 136 | } 137 | listeners[last].Second = nil 138 | es.listeners[eventType] = listeners[:last] 139 | delete(es.mapping, id) 140 | return true 141 | } 142 | 143 | // HasListener implements the Dispatcher interface. 144 | func (es *eventSystem[T]) HasListener(id ListenerID) bool { 145 | _, ok := es.mapping[id] 146 | return ok 147 | } 148 | 149 | // DispatchEvent implements the Dispatcher interface. 150 | func (es *eventSystem[T]) DispatchEvent(ctx context.Context, event Event[T]) error { 151 | listeners, ok := es.listeners[event.Typeof()] 152 | if !ok || len(listeners) == 0 { 153 | return nil 154 | } 155 | var errs []error 156 | for i := range listeners { 157 | errs = append(errs, listeners[i].Second.HandleEvent(ctx, event)) 158 | } 159 | return errors.Join(errs...) 160 | } 161 | 162 | // Register registers an event type for encoding and decoding. 163 | func Register[T comparable](event Event[T]) { 164 | gob.Register(event) 165 | } 166 | 167 | // Encoder encodes events to a writer. 168 | type Encoder struct { 169 | encoder *gob.Encoder 170 | } 171 | 172 | // NewEncoder creates a new Encoder instance. 173 | func NewEncoder(w io.Writer) *Encoder { 174 | return &Encoder{gob.NewEncoder(w)} 175 | } 176 | 177 | // Encode encodes an event to the given encoder. 178 | func Encode[T comparable](enc *Encoder, event Event[T]) error { 179 | return enc.encoder.Encode(event) 180 | } 181 | 182 | // EncodeTo encodes an event to the given writer. 183 | func EncodeTo[T comparable](w io.Writer, event Event[T]) error { 184 | return Encode(NewEncoder(w), event) 185 | } 186 | 187 | // Decoder decodes events from a reader. 188 | type Decoder struct { 189 | decoder *gob.Decoder 190 | } 191 | 192 | // NewDecoder creates a new Decoder instance. 193 | func NewDecoder(r io.Reader) *Decoder { 194 | return &Decoder{gob.NewDecoder(r)} 195 | } 196 | 197 | // Decode decodes an event from the given reader. 198 | func Decode[T comparable](dec *Decoder, event Event[T]) error { 199 | return dec.decoder.Decode(event) 200 | } 201 | 202 | // DecodeFrom decodes an event from the given reader. 203 | func DecodeFrom[T comparable](r io.Reader, event Event[T]) error { 204 | return Decode(NewDecoder(r), event) 205 | } 206 | -------------------------------------------------------------------------------- /flags/flags.go: -------------------------------------------------------------------------------- 1 | // Package flags provides custom flag types and utilities. 2 | package flags 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "slices" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/gopherd/core/term" 12 | ) 13 | 14 | // Map is a map of string key-value pairs that implements the flag.Value interface. 15 | type Map map[string]string 16 | 17 | // Get returns the value of the key. 18 | func (m Map) Get(k string) string { 19 | if m == nil { 20 | return "" 21 | } 22 | return m[k] 23 | } 24 | 25 | // Contains reports whether the key is in the map. 26 | func (m Map) Contains(k string) bool { 27 | if m == nil { 28 | return false 29 | } 30 | _, ok := m[k] 31 | return ok 32 | } 33 | 34 | // Lookup returns the value of the key and reports whether the key is in the map. 35 | func (m Map) Lookup(k string) (string, bool) { 36 | if m == nil { 37 | return "", false 38 | } 39 | v, ok := m[k] 40 | return v, ok 41 | } 42 | 43 | // Set implements the flag.Value interface. 44 | func (m *Map) Set(s string) error { 45 | if *m == nil { 46 | *m = make(Map) 47 | } 48 | var k, v string 49 | index := strings.Index(s, "=") 50 | if index < 0 { 51 | k = s 52 | } else { 53 | k, v = s[:index], s[index+1:] 54 | } 55 | if _, dup := (*m)[k]; dup { 56 | return fmt.Errorf("already set: %q", k) 57 | } 58 | if k == "" { 59 | return fmt.Errorf("invalid format: %q, expect key[=value]", s) 60 | } 61 | (*m)[k] = v 62 | return nil 63 | } 64 | 65 | // String implements the flag.Value interface. 66 | func (m Map) String() string { 67 | if len(m) == 0 { 68 | return "" 69 | } 70 | keys := make([]string, 0, len(m)) 71 | for k := range m { 72 | keys = append(keys, k) 73 | } 74 | slices.Sort(keys) 75 | 76 | var sb strings.Builder 77 | for _, k := range keys { 78 | if sb.Len() > 0 { 79 | sb.WriteByte(',') 80 | } 81 | v := m[k] 82 | if needsQuoting(k) { 83 | sb.WriteString(strconv.Quote(k)) 84 | } else { 85 | sb.WriteString(k) 86 | } 87 | if v != "" { 88 | sb.WriteByte('=') 89 | if needsQuoting(v) { 90 | sb.WriteString(strconv.Quote(v)) 91 | } else { 92 | sb.WriteString(v) 93 | } 94 | } 95 | } 96 | return sb.String() 97 | } 98 | 99 | // IsAllValuesSet reports whether all values are set. 100 | func (m Map) IsAllValuesSet() bool { 101 | for _, v := range m { 102 | if v == "" { 103 | return false 104 | } 105 | } 106 | return true 107 | } 108 | 109 | // Slice is a slice of strings that implements the flag.Value interface. 110 | type Slice []string 111 | 112 | // Set implements the flag.Value interface. 113 | func (s *Slice) Set(v string) error { 114 | if v == "" { 115 | return fmt.Errorf("empty value") 116 | } 117 | *s = append(*s, v) 118 | return nil 119 | } 120 | 121 | // String implements the flag.Value interface. 122 | func (s Slice) String() string { 123 | var sb strings.Builder 124 | for i, v := range s { 125 | if i > 0 { 126 | sb.WriteByte(',') 127 | } 128 | if needsQuoting(v) { 129 | sb.WriteString(strconv.Quote(v)) 130 | } else { 131 | sb.WriteString(v) 132 | } 133 | } 134 | return sb.String() 135 | } 136 | 137 | // MapSlice is a map of string key-slice pairs that implements the flag.Value interface. 138 | // It is used to parse multiple values for the same key. 139 | type MapSlice map[string]Slice 140 | 141 | // Get returns the slice of the key. 142 | func (m MapSlice) Get(k string) Slice { 143 | if m == nil { 144 | return nil 145 | } 146 | return m[k] 147 | } 148 | 149 | // Contains reports whether the key is in the map. 150 | func (m MapSlice) Contains(k string) bool { 151 | if m == nil { 152 | return false 153 | } 154 | _, ok := m[k] 155 | return ok 156 | } 157 | 158 | // Lookup returns the slice of the key and reports whether the key is in the map. 159 | func (m MapSlice) Lookup(k string) (Slice, bool) { 160 | if m == nil { 161 | return nil, false 162 | } 163 | v, ok := m[k] 164 | return v, ok 165 | } 166 | 167 | // Set implements the flag.Value interface. 168 | func (m *MapSlice) Set(s string) error { 169 | if *m == nil { 170 | *m = make(MapSlice) 171 | } 172 | var k, v string 173 | index := strings.Index(s, "=") 174 | if index <= 0 || index == len(s)-1 { 175 | return fmt.Errorf("invalid format: %q, expect key=value", s) 176 | } 177 | k, v = s[:index], s[index+1:] 178 | if k == "" { 179 | return fmt.Errorf("invalid format: %q, expect key=value", s) 180 | } 181 | if v == "" { 182 | return fmt.Errorf("invalid format: %q, expect key=value", s) 183 | } 184 | (*m)[k] = append((*m)[k], v) 185 | return nil 186 | } 187 | 188 | func (m MapSlice) String() string { 189 | if len(m) == 0 { 190 | return "" 191 | } 192 | var sb strings.Builder 193 | for k, vs := range m { 194 | if sb.Len() > 0 { 195 | sb.WriteByte(',') 196 | } 197 | if needsQuoting(k) { 198 | sb.WriteString(strconv.Quote(k)) 199 | } else { 200 | sb.WriteString(k) 201 | } 202 | sb.WriteByte('=') 203 | sb.WriteString(vs.String()) 204 | } 205 | return sb.String() 206 | } 207 | 208 | type options struct { 209 | nameColor term.Color 210 | newline bool 211 | } 212 | 213 | // Option is an option for flag types. 214 | type Option func(*options) 215 | 216 | // NameColor sets the color of command names. 217 | func NameColor(c term.Color) Option { 218 | return func(opts *options) { 219 | opts.nameColor = c 220 | } 221 | } 222 | 223 | // Newline adds a newline after the usage text. 224 | func Newline() Option { 225 | return func(opts *options) { 226 | opts.newline = true 227 | } 228 | } 229 | 230 | // UsageFunc is a function that formats usage text. 231 | type UsageFunc func(usage string) string 232 | 233 | // UseUsage returns a UsageFunc that formats usage text with colorized command names. 234 | func UseUsage(w io.Writer, opts ...Option) UsageFunc { 235 | o := options{} 236 | for _, opt := range opts { 237 | opt(&o) 238 | } 239 | return func(usage string) string { 240 | return formatUsage(w, usage, o) 241 | } 242 | } 243 | 244 | // formatUsage returns a usage string with colorized command names. 245 | func formatUsage(w io.Writer, usage string, opt options) string { 246 | if opt.newline { 247 | usage += "\n" 248 | } 249 | if !term.IsTerminal(w) || !term.IsSupportsAnsi() || !term.IsSupports256Colors() { 250 | return usage 251 | } 252 | for i := 0; i < len(usage); i++ { 253 | if usage[i] == '`' { 254 | for j := i + 1; j < len(usage); j++ { 255 | if usage[j] == '`' { 256 | return usage[:i+1] + opt.nameColor.Format(usage[i+1:j]) + usage[j:] 257 | } 258 | } 259 | break 260 | } 261 | } 262 | return usage 263 | } 264 | -------------------------------------------------------------------------------- /flags/util.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | import ( 4 | "unicode" 5 | "unicode/utf8" 6 | ) 7 | 8 | func needsQuoting(s string) bool { 9 | if len(s) == 0 { 10 | return true 11 | } 12 | for i := 0; i < len(s); { 13 | b := s[i] 14 | if b < utf8.RuneSelf { 15 | // Quote anything except a backslash that would need quoting in a 16 | // JSON string, as well as space and '=' 17 | if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) { 18 | return true 19 | } 20 | i++ 21 | continue 22 | } 23 | r, size := utf8.DecodeRuneInString(s[i:]) 24 | if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) { 25 | return true 26 | } 27 | i += size 28 | } 29 | return false 30 | } 31 | 32 | var safeSet = [utf8.RuneSelf]bool{ 33 | ' ': true, 34 | '!': true, 35 | '"': false, 36 | '#': true, 37 | '$': true, 38 | '%': true, 39 | '&': true, 40 | '\'': true, 41 | '(': true, 42 | ')': true, 43 | '*': true, 44 | '+': true, 45 | ',': false, 46 | '-': true, 47 | '.': true, 48 | '/': true, 49 | '0': true, 50 | '1': true, 51 | '2': true, 52 | '3': true, 53 | '4': true, 54 | '5': true, 55 | '6': true, 56 | '7': true, 57 | '8': true, 58 | '9': true, 59 | ':': true, 60 | ';': true, 61 | '<': true, 62 | '=': true, 63 | '>': true, 64 | '?': true, 65 | '@': true, 66 | 'A': true, 67 | 'B': true, 68 | 'C': true, 69 | 'D': true, 70 | 'E': true, 71 | 'F': true, 72 | 'G': true, 73 | 'H': true, 74 | 'I': true, 75 | 'J': true, 76 | 'K': true, 77 | 'L': true, 78 | 'M': true, 79 | 'N': true, 80 | 'O': true, 81 | 'P': true, 82 | 'Q': true, 83 | 'R': true, 84 | 'S': true, 85 | 'T': true, 86 | 'U': true, 87 | 'V': true, 88 | 'W': true, 89 | 'X': true, 90 | 'Y': true, 91 | 'Z': true, 92 | '[': true, 93 | '\\': false, 94 | ']': true, 95 | '^': true, 96 | '_': true, 97 | '`': true, 98 | 'a': true, 99 | 'b': true, 100 | 'c': true, 101 | 'd': true, 102 | 'e': true, 103 | 'f': true, 104 | 'g': true, 105 | 'h': true, 106 | 'i': true, 107 | 'j': true, 108 | 'k': true, 109 | 'l': true, 110 | 'm': true, 111 | 'n': true, 112 | 'o': true, 113 | 'p': true, 114 | 'q': true, 115 | 'r': true, 116 | 's': true, 117 | 't': true, 118 | 'u': true, 119 | 'v': true, 120 | 'w': true, 121 | 'x': true, 122 | 'y': true, 123 | 'z': true, 124 | '{': true, 125 | '|': true, 126 | '}': true, 127 | '~': true, 128 | '\u007f': true, 129 | } 130 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gopherd/core 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /lifecycle/lifecycle.go: -------------------------------------------------------------------------------- 1 | // Package lifecycle provides interfaces and types for managing the lifecycle of components. 2 | package lifecycle 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | ) 8 | 9 | // Status represents the lifecycle state of a component. 10 | type Status int 11 | 12 | const ( 13 | // Created indicates that the component has been instantiated but not yet initialized. 14 | Created Status = iota 15 | // Starting indicates that the component is in the process of starting. 16 | Starting 17 | // Running indicates that the component is fully operational. 18 | Running 19 | // Stopping indicates that the component is in the process of shutting down. 20 | Stopping 21 | // Closed indicates that the component has been fully shut down. 22 | Closed 23 | ) 24 | 25 | // String returns a string representation of the Status. 26 | func (s Status) String() string { 27 | switch s { 28 | case Created: 29 | return "Created" 30 | case Starting: 31 | return "Starting" 32 | case Running: 33 | return "Running" 34 | case Stopping: 35 | return "Stopping" 36 | case Closed: 37 | return "Closed" 38 | default: 39 | return fmt.Sprintf("Unknown(%d)", int(s)) 40 | } 41 | } 42 | 43 | // Lifecycle defines the interface for components with lifecycle management. 44 | type Lifecycle interface { 45 | // Init initializes the component. 46 | Init(context.Context) error 47 | // Uninit performs cleanup after the component is no longer needed. 48 | Uninit(context.Context) error 49 | // Start begins the component's main operations. 50 | Start(context.Context) error 51 | // Shutdown gracefully stops the component's operations. 52 | Shutdown(context.Context) error 53 | } 54 | 55 | // Funcs represents a set of lifecycle functions for a simple component. 56 | type Funcs struct { 57 | Init func(context.Context) error 58 | Start func(context.Context) error 59 | Shutdown func(context.Context) error 60 | Uninit func(context.Context) error 61 | } 62 | 63 | // BaseLifecycle provides a default implementation of the Lifecycle interface. 64 | type BaseLifecycle struct{} 65 | 66 | // Init implements the Init method of the Lifecycle interface. 67 | func (*BaseLifecycle) Init(context.Context) error { 68 | return nil 69 | } 70 | 71 | // Uninit implements the Uninit method of the Lifecycle interface. 72 | func (*BaseLifecycle) Uninit(context.Context) error { 73 | return nil 74 | } 75 | 76 | // Start implements the Start method of the Lifecycle interface. 77 | func (*BaseLifecycle) Start(context.Context) error { 78 | return nil 79 | } 80 | 81 | // Shutdown implements the Shutdown method of the Lifecycle interface. 82 | func (*BaseLifecycle) Shutdown(context.Context) error { 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /lifecycle/lifecycle_test.go: -------------------------------------------------------------------------------- 1 | package lifecycle_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/gopherd/core/lifecycle" 8 | ) 9 | 10 | func TestBaseLifecycle(t *testing.T) { 11 | var l lifecycle.BaseLifecycle 12 | if err := l.Init(context.Background()); err != nil { 13 | t.Errorf("BaseLifecycle.Init() error = %v, want nil", err) 14 | } 15 | if err := l.Start(context.Background()); err != nil { 16 | t.Errorf("BaseLifecycle.Start() error = %v, want nil", err) 17 | } 18 | if err := l.Shutdown(context.Background()); err != nil { 19 | t.Errorf("BaseLifecycle.Shutdown() error = %v, want nil", err) 20 | } 21 | if err := l.Uninit(context.Background()); err != nil { 22 | t.Errorf("BaseLifecycle.Uninit() error = %v, want nil", err) 23 | } 24 | } 25 | 26 | func TestStatus(t *testing.T) { 27 | tests := []struct { 28 | name string 29 | s lifecycle.Status 30 | want string 31 | }{ 32 | { 33 | name: "Created", 34 | s: lifecycle.Created, 35 | want: "Created", 36 | }, 37 | { 38 | name: "Starting", 39 | s: lifecycle.Starting, 40 | want: "Starting", 41 | }, 42 | { 43 | name: "Running", 44 | s: lifecycle.Running, 45 | want: "Running", 46 | }, 47 | { 48 | name: "Stopping", 49 | s: lifecycle.Stopping, 50 | want: "Stopping", 51 | }, 52 | { 53 | name: "Closed", 54 | s: lifecycle.Closed, 55 | want: "Closed", 56 | }, 57 | { 58 | name: "Unknown", 59 | s: lifecycle.Status(100), 60 | want: "Unknown(100)", 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | if got := tt.s.String(); got != tt.want { 66 | t.Errorf("Status.String() = %v, want %v", got, tt.want) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /math/mathutil/mathutil.go: -------------------------------------------------------------------------------- 1 | // Package mathutil provides various mathematical utility functions. 2 | package mathutil 3 | 4 | import ( 5 | "cmp" 6 | "math" 7 | 8 | "github.com/gopherd/core/constraints" 9 | ) 10 | 11 | // Abs returns the absolute value of x. 12 | func Abs[T constraints.SignedReal](x T) T { 13 | if x < 0 { 14 | return -x 15 | } 16 | return x 17 | } 18 | 19 | // Predict returns 1 if ok is true, otherwise 0. 20 | func Predict[T constraints.Integer | constraints.Float](ok bool) T { 21 | if ok { 22 | return 1 23 | } 24 | return 0 25 | } 26 | 27 | // Clamp restricts x to the range [min, max]. 28 | func Clamp[T cmp.Ordered](x, min, max T) T { 29 | if cmp.Less(x, min) { 30 | return min 31 | } 32 | if cmp.Less(max, x) { 33 | return max 34 | } 35 | return x 36 | } 37 | 38 | // EuclideanModulo computes the Euclidean modulo of x % y. 39 | func EuclideanModulo[T constraints.Float](x, y T) T { 40 | return T(math.Mod(math.Mod(float64(x), float64(y))+float64(y), float64(y))) 41 | } 42 | 43 | // MapLinear performs linear mapping from range [a1, a2] to range [b1, b2]. 44 | func MapLinear[T constraints.Field](x, a1, a2, b1, b2 T) T { 45 | return b1 + (x-a1)*(b2-b1)/(a2-a1) 46 | } 47 | 48 | // Lerp performs linear interpolation between x and y based on t. 49 | func Lerp[T constraints.Field](x, y, t T) T { 50 | return (1-t)*x + t*y 51 | } 52 | 53 | // InverseLerp calculates the inverse of linear interpolation. 54 | func InverseLerp[T constraints.Field](x, y, value T) T { 55 | if x != y { 56 | return (value - x) / (y - x) 57 | } 58 | return 0 59 | } 60 | 61 | // Damp performs frame rate independent damping. 62 | func Damp[T constraints.Float](x, y, lambda, dt T) T { 63 | return Lerp(x, y, 1-T(math.Exp(-float64(lambda*dt)))) 64 | } 65 | 66 | // PingPong calculates a value that ping-pongs between 0 and length. 67 | func PingPong[T constraints.Float](x, length T) T { 68 | return length - Abs(EuclideanModulo(x, length*2)-length) 69 | } 70 | 71 | // SmoothStep performs smooth interpolation between min and max. 72 | func SmoothStep[T constraints.Float](x, min, max T) T { 73 | if x <= min { 74 | return 0 75 | } 76 | if x >= max { 77 | return 1 78 | } 79 | x = (x - min) / (max - min) 80 | return x * x * (3 - 2*x) 81 | } 82 | 83 | // SmoothStepFunc applies a custom function to the smoothstep interpolation. 84 | func SmoothStepFunc[T constraints.Float](x, min, max T, fn func(T) T) T { 85 | if x <= min { 86 | return 0 87 | } 88 | if x >= max { 89 | return 1 90 | } 91 | x = (x - min) / (max - min) 92 | return fn(x) 93 | } 94 | 95 | // IsPowerOfTwo checks if the given value is a power of two. 96 | func IsPowerOfTwo[T constraints.Integer](value T) bool { 97 | return value > 0 && (value&(value-1)) == 0 98 | } 99 | 100 | // UpperPow2 returns the smallest power of 2 greater than or equal to n. 101 | func UpperPow2(n int) int { 102 | n-- 103 | n |= n >> 1 104 | n |= n >> 2 105 | n |= n >> 4 106 | n |= n >> 8 107 | n |= n >> 16 108 | return n + 1 109 | } 110 | 111 | // Deg2Rad converts degrees to radians. 112 | func Deg2Rad[T constraints.Float](deg T) T { 113 | return deg * T(math.Pi) / 180 114 | } 115 | 116 | // Rad2Deg converts radians to degrees. 117 | func Rad2Deg[T constraints.Float](rad T) T { 118 | return rad * 180 / T(math.Pi) 119 | } 120 | 121 | // UnaryFn represents a unary function. 122 | type UnaryFn[T constraints.Number] func(T) T 123 | 124 | // Add returns a new UnaryFn that adds the results of two UnaryFn. 125 | func (f UnaryFn[T]) Add(f2 UnaryFn[T]) UnaryFn[T] { 126 | return func(x T) T { 127 | return f(x) + f2(x) 128 | } 129 | } 130 | 131 | // Sub returns a new UnaryFn that subtracts the result of f2 from f. 132 | func (f UnaryFn[T]) Sub(f2 UnaryFn[T]) UnaryFn[T] { 133 | return func(x T) T { 134 | return f(x) - f2(x) 135 | } 136 | } 137 | 138 | // Mul returns a new UnaryFn that multiplies the results of two UnaryFn. 139 | func (f UnaryFn[T]) Mul(f2 UnaryFn[T]) UnaryFn[T] { 140 | return func(x T) T { 141 | return f(x) * f2(x) 142 | } 143 | } 144 | 145 | // Div returns a new UnaryFn that divides the result of f by f2. 146 | func (f UnaryFn[T]) Div(f2 UnaryFn[T]) UnaryFn[T] { 147 | return func(x T) T { 148 | return f(x) / f2(x) 149 | } 150 | } 151 | 152 | // Constant returns a UnaryFn that always returns c. 153 | func Constant[T constraints.Number](c T) UnaryFn[T] { 154 | return func(T) T { return c } 155 | } 156 | 157 | // KSigmoid returns a UnaryFn that applies a sigmoid function with slope k. 158 | func KSigmoid[T constraints.Real](k T) UnaryFn[T] { 159 | return func(x T) T { return Sigmoid(k * x) } 160 | } 161 | 162 | // KSigmoidPrime returns a UnaryFn that applies the derivative of a sigmoid function with slope k. 163 | func KSigmoidPrime[T constraints.Real](k T) UnaryFn[T] { 164 | return func(x T) T { return SigmoidPrime(k*x) * k } 165 | } 166 | 167 | // Scale returns a UnaryFn that scales its input by k. 168 | func Scale[T constraints.Number](k T) UnaryFn[T] { 169 | return func(x T) T { return k * x } 170 | } 171 | 172 | // Offset returns a UnaryFn that adds b to its input. 173 | func Offset[T constraints.Number](b T) UnaryFn[T] { 174 | return func(x T) T { return x + b } 175 | } 176 | 177 | // Affine returns a UnaryFn that applies an affine transformation (kx + b). 178 | func Affine[T constraints.Number](k, b T) UnaryFn[T] { 179 | return func(x T) T { return k*x + b } 180 | } 181 | 182 | // Power returns a UnaryFn that raises its input to the power of p. 183 | func Power[T constraints.Real](p T) UnaryFn[T] { 184 | return func(x T) T { return T(math.Pow(float64(x), float64(p))) } 185 | } 186 | 187 | // Zero always returns 0. 188 | func Zero[T constraints.Number](T) T { 189 | return 0 190 | } 191 | 192 | // One always returns 1. 193 | func One[T constraints.Number](T) T { 194 | return 1 195 | } 196 | 197 | // Identity returns its input unchanged. 198 | func Identity[T constraints.Number](x T) T { 199 | return x 200 | } 201 | 202 | // Square returns the square of its input. 203 | func Square[T constraints.Number](x T) T { 204 | return x * x 205 | } 206 | 207 | // IsZero returns 1 if the input is zero, otherwise 0. 208 | func IsZero[T constraints.SignedReal](x T) T { 209 | if x == 0 { 210 | return 1 211 | } 212 | return 0 213 | } 214 | 215 | // Sign returns the sign of the input (-1, 0, or 1). 216 | func Sign[T constraints.SignedReal](x T) T { 217 | switch { 218 | case x < 0: 219 | return -1 220 | case x > 0: 221 | return 1 222 | default: 223 | return 0 224 | } 225 | } 226 | 227 | // Sigmoid applies the sigmoid function to the input. 228 | func Sigmoid[T constraints.Real](x T) T { 229 | return T(1.0 / (1.0 + math.Exp(-float64(x)))) 230 | } 231 | 232 | // SigmoidPrime applies the derivative of the sigmoid function to the input. 233 | func SigmoidPrime[T constraints.Real](x T) T { 234 | sx := Sigmoid(x) 235 | return sx * (1 - sx) 236 | } 237 | 238 | // BinaryFn represents a binary function. 239 | type BinaryFn[T constraints.Number] func(x, y T) T 240 | 241 | // Add returns a new BinaryFn that adds the results of two BinaryFn. 242 | func (f BinaryFn[T]) Add(f2 BinaryFn[T]) BinaryFn[T] { 243 | return func(x, y T) T { 244 | return f(x, y) + f2(x, y) 245 | } 246 | } 247 | 248 | // Sub returns a new BinaryFn that subtracts the result of f2 from f. 249 | func (f BinaryFn[T]) Sub(f2 BinaryFn[T]) BinaryFn[T] { 250 | return func(x, y T) T { 251 | return f(x, y) - f2(x, y) 252 | } 253 | } 254 | 255 | // Mul returns a new BinaryFn that multiplies the results of two BinaryFn. 256 | func (f BinaryFn[T]) Mul(f2 BinaryFn[T]) BinaryFn[T] { 257 | return func(x, y T) T { 258 | return f(x, y) * f2(x, y) 259 | } 260 | } 261 | 262 | // Div returns a new BinaryFn that divides the result of f by f2. 263 | func (f BinaryFn[T]) Div(f2 BinaryFn[T]) BinaryFn[T] { 264 | return func(x, y T) T { 265 | return f(x, y) / f2(x, y) 266 | } 267 | } 268 | 269 | // Add returns the sum of x and y. 270 | func Add[T constraints.Number](x, y T) T { return x + y } 271 | 272 | // Sub returns the difference of x and y. 273 | func Sub[T constraints.Number](x, y T) T { return x - y } 274 | 275 | // Mul returns the product of x and y. 276 | func Mul[T constraints.Number](x, y T) T { return x * y } 277 | 278 | // Div returns the quotient of x and y. 279 | func Div[T constraints.Number](x, y T) T { return x / y } 280 | 281 | // Pow returns x raised to the power of y. 282 | func Pow[T constraints.Real](x, y T) T { return T(math.Pow(float64(x), float64(y))) } 283 | 284 | // ClampedLerp performs a linear interpolation and clamps the result. 285 | func ClampedLerp[T constraints.Float](x, y, t, min, max T) T { 286 | return Clamp(Lerp(x, y, t), min, max) 287 | } 288 | -------------------------------------------------------------------------------- /math/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | // Uint64NGenerator is an interface for generating random uint64 numbers in the range [0, n). 4 | type Uint64NGenerator interface { 5 | Uint64N(n uint64) uint64 6 | } 7 | 8 | // Shuffle randomly shuffles the elements in the slice. 9 | func Shuffle[R Uint64NGenerator, S ~[]T, T any](r R, s S) S { 10 | return ShuffleN(r, s, len(s)) 11 | } 12 | 13 | // ShuffleN randomly selects and shuffles the first n elements from the entire slice. 14 | // It ensures that the first n elements are randomly chosen from the whole slice, 15 | // not just shuffled among themselves. Elements after the nth position may also be affected. 16 | // This differs from a complete shuffle as it only guarantees randomness for the first n elements. 17 | // It panics if n is negative or greater than the length of the slice. 18 | func ShuffleN[R Uint64NGenerator, S ~[]T, T any](r R, s S, n int) S { 19 | if n < 0 || n > len(s) { 20 | panic("random.ShuffleN: invalid number of elements to shuffle") 21 | } 22 | for i := 0; i < n; i++ { 23 | j := int(r.Uint64N(uint64(i + 1))) 24 | s[i], s[j] = s[j], s[i] 25 | } 26 | return s 27 | } 28 | -------------------------------------------------------------------------------- /math/random/random_test.go: -------------------------------------------------------------------------------- 1 | package random_test 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "slices" 7 | "sort" 8 | "testing" 9 | 10 | "github.com/gopherd/core/math/random" 11 | ) 12 | 13 | type mockRand struct { 14 | } 15 | 16 | func (r *mockRand) Uint64N(n uint64) uint64 { 17 | return rand.Uint64() % n 18 | } 19 | 20 | func TestShuffle(t *testing.T) { 21 | r := new(mockRand) 22 | original := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 23 | shuffled := slices.Clone(original) 24 | random.Shuffle(r, shuffled) 25 | 26 | if reflect.DeepEqual(original, shuffled) { 27 | t.Errorf("Shuffle() did not change the order of elements") 28 | } 29 | 30 | if len(original) != len(shuffled) { 31 | t.Errorf("Shuffle() changed the length of the slice") 32 | } 33 | 34 | originalSet := make(map[int]bool) 35 | shuffledSet := make(map[int]bool) 36 | for i := range original { 37 | originalSet[original[i]] = true 38 | shuffledSet[shuffled[i]] = true 39 | } 40 | 41 | if !reflect.DeepEqual(originalSet, shuffledSet) { 42 | t.Errorf("Shuffle() changed the elements in the slice") 43 | } 44 | } 45 | 46 | func TestShuffleN(t *testing.T) { 47 | r := new(mockRand) 48 | original := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 49 | shuffled := slices.Clone(original) 50 | n := 5 51 | random.ShuffleN(r, shuffled, n) 52 | 53 | // Check that the length hasn't changed 54 | if len(original) != len(shuffled) { 55 | t.Errorf("ShuffleN() changed the length of the slice") 56 | } 57 | 58 | // Check that all original elements are still present 59 | sortedOriginal := slices.Clone(original) 60 | sortedShuffled := slices.Clone(shuffled) 61 | sort.Ints(sortedOriginal) 62 | sort.Ints(sortedShuffled) 63 | if !reflect.DeepEqual(sortedOriginal, sortedShuffled) { 64 | t.Errorf("ShuffleN() changed the elements in the slice") 65 | } 66 | 67 | // Check that elements after n are not guaranteed to be in their original positions 68 | // (Again, there's a small chance this could fail even with correct implementation) 69 | allSame := true 70 | for i := n; i < len(original); i++ { 71 | if original[i] != shuffled[i] { 72 | allSame = false 73 | break 74 | } 75 | } 76 | if !allSame { 77 | t.Errorf("ShuffleN() did not affect any elements after position %d", n) 78 | } 79 | 80 | t.Run("PanicOnNegativeN", func(t *testing.T) { 81 | defer func() { 82 | if r := recover(); r == nil { 83 | t.Errorf("ShuffleN() did not panic on negative n") 84 | } 85 | }() 86 | random.ShuffleN(r, []int{1, 2, 3}, -1) 87 | }) 88 | 89 | t.Run("PanicOnLargeN", func(t *testing.T) { 90 | defer func() { 91 | if r := recover(); r == nil { 92 | t.Errorf("ShuffleN() did not panic when n > len(slice)") 93 | } 94 | }() 95 | random.ShuffleN(r, []int{1, 2, 3}, 4) 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /op/op.go: -------------------------------------------------------------------------------- 1 | // Package op provides a set of generic functions that extend 2 | // and complement Go's built-in operators and basic operations. 3 | // It includes utilities for conditional logic, comparisons, and type manipulations. 4 | package op 5 | 6 | import "fmt" 7 | 8 | // Or returns b if a is the zero value for T, otherwise returns a. 9 | func Or[T comparable](a, b T) T { 10 | var zero T 11 | if a == zero { 12 | return b 13 | } 14 | return a 15 | } 16 | 17 | // OrFunc returns the result of calling b() if a is the zero value for T, 18 | // otherwise returns a. It allows for lazy evaluation of the alternative value. 19 | func OrFunc[T comparable](a T, b func() T) T { 20 | var zero T 21 | if a == zero { 22 | return b() 23 | } 24 | return a 25 | } 26 | 27 | // SetDefault sets the value of a to b if a is the zero value for T. 28 | // It returns the final value of a. 29 | func SetDefault[T comparable](a *T, b T) T { 30 | var zero T 31 | if *a == zero { 32 | *a = b 33 | } 34 | return *a 35 | } 36 | 37 | // SetDefaultFunc sets the value of a to the result of calling b() if a is 38 | // the zero value for T. It returns the final value of a. 39 | func SetDefaultFunc[T comparable](a *T, b func() T) T { 40 | var zero T 41 | if *a == zero { 42 | *a = b() 43 | } 44 | return *a 45 | } 46 | 47 | // If returns a if condition is true, otherwise returns b. 48 | // It provides a generic ternary operation for any type. 49 | func If[T any](condition bool, a, b T) T { 50 | if condition { 51 | return a 52 | } 53 | return b 54 | } 55 | 56 | // IfFunc returns a if condition is true, otherwise returns the result of calling b(). 57 | func IfFunc[T any](condition bool, a T, b func() T) T { 58 | if condition { 59 | return a 60 | } 61 | return b() 62 | } 63 | 64 | // IfFunc2 returns the result of calling a() if condition is true, 65 | // otherwise returns the result of calling b(). 66 | // It allows for lazy evaluation of both alternatives. 67 | func IfFunc2[T any](condition bool, a, b func() T) T { 68 | if condition { 69 | return a() 70 | } 71 | return b() 72 | } 73 | 74 | // Bin converts a comparable value to a binary number (0 or 1). 75 | // It returns 0 if the input is equal to its zero value, and 1 otherwise. 76 | func Bin[T comparable](x T) int { 77 | var zero T 78 | if x == zero { 79 | return 0 80 | } 81 | return 1 82 | } 83 | 84 | // First returns the first argument. 85 | // It extracts the first value from a set of arguments. 86 | func First[T any](first T, _ ...any) T { 87 | return first 88 | } 89 | 90 | // Second returns the second argument. 91 | // It extracts the second value from a set of arguments. 92 | func Second[T1, T2 any](first T1, second T2, _ ...any) T2 { 93 | return second 94 | } 95 | 96 | // Third returns the third argument. 97 | // It extracts the third value from a set of arguments. 98 | func Third[T1, T2, T3 any](first T1, second T2, third T3, _ ...any) T3 { 99 | return third 100 | } 101 | 102 | // Deref returns the value of p if it is not nil, otherwise it returns the zero value of T. 103 | func Deref[T any](p *T) T { 104 | var zero T 105 | if p == nil { 106 | return zero 107 | } 108 | return *p 109 | } 110 | 111 | // DerefOr returns the value of p if it is not nil, otherwise it returns x. 112 | func DerefOr[T any](p *T, x T) T { 113 | if p == nil { 114 | return x 115 | } 116 | return *p 117 | } 118 | 119 | // DerefOr returns the value of p if it is not nil, otherwise it returns result of calling x(). 120 | func DerefOrFunc[T any](p *T, x func() T) T { 121 | if p == nil { 122 | return x() 123 | } 124 | return *p 125 | } 126 | 127 | // Addr returns the address of x. 128 | func Addr[T any](x T) *T { 129 | return &x 130 | } 131 | 132 | // Must panics if err is not nil. 133 | func Must(err error) { 134 | if err != nil { 135 | panic(err) 136 | } 137 | } 138 | 139 | // Result returns err if it is not nil, otherwise it returns value. 140 | func Result(value any, err error) any { 141 | if err != nil { 142 | return err 143 | } 144 | return value 145 | } 146 | 147 | // MustResult panics if err is not nil, otherwise it returns value. 148 | // It is a convenient way to handle errors in a single line. 149 | func MustResult[T any](value T, err error) T { 150 | if err != nil { 151 | panic(err) 152 | } 153 | return value 154 | } 155 | 156 | // MustResult2 panics if err is not nil, otherwise it returns value1 and value2. 157 | func MustResult2[T1, T2 any](value1 T1, value2 T2, err error) (T1, T2) { 158 | if err != nil { 159 | panic(err) 160 | } 161 | return value1, value2 162 | } 163 | 164 | // Assert panics if cond is false. 165 | func Assert(cond bool, msgs ...any) { 166 | if !cond { 167 | msg := "assertion failed" 168 | if len(msgs) > 0 { 169 | msg = fmt.Sprint(msgs...) 170 | } 171 | panic(msg) 172 | } 173 | } 174 | 175 | // Assertf panics with a formatted message if cond is false. 176 | func Assertf(cond bool, format string, args ...any) { 177 | if !cond { 178 | panic(fmt.Sprintf(format, args...)) 179 | } 180 | } 181 | 182 | // ReverseCompare returns a comparison function that reverses the order of the original comparison function. 183 | func ReverseCompare[T any](cmp func(T, T) int) func(T, T) int { 184 | return func(x, y T) int { 185 | return cmp(y, x) 186 | } 187 | } 188 | 189 | // Zero returns the zero value of type T. 190 | func Zero[T any]() T { 191 | var zero T 192 | return zero 193 | } 194 | 195 | // Identity returns a function that returns the input value. 196 | func Identity[T any](v T) func() T { 197 | return func() T { 198 | return v 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /service/config.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "os" 12 | "strings" 13 | "time" 14 | 15 | "github.com/gopherd/core/component" 16 | "github.com/gopherd/core/encoding" 17 | "github.com/gopherd/core/op" 18 | "github.com/gopherd/core/text/templates" 19 | ) 20 | 21 | // Config represents a generic configuration structure for services. 22 | // It includes a context of type T and a list of component configurations. 23 | type Config[T any] struct { 24 | Context T `json:",omitempty"` 25 | Components []component.Config `json:",omitempty"` 26 | } 27 | 28 | // load processes the configuration based on the provided source. 29 | // It returns an error if the configuration cannot be loaded or decoded. 30 | func (c *Config[T]) load(stdin io.Reader, decoder encoding.Decoder, source string) error { 31 | if source == "" { 32 | return nil 33 | } 34 | 35 | var r io.Reader 36 | var err error 37 | 38 | switch { 39 | case source == "-": 40 | r = stdin 41 | case strings.HasPrefix(source, "http://"), strings.HasPrefix(source, "https://"): 42 | var b io.ReadCloser 43 | b, err = c.loadFromHTTP(source, time.Second*10) 44 | if err == nil { 45 | defer b.Close() 46 | } 47 | r = b 48 | default: 49 | var f io.ReadCloser 50 | f, err = os.Open(source) 51 | if err == nil { 52 | defer f.Close() 53 | } 54 | r = f 55 | } 56 | 57 | if err != nil { 58 | return fmt.Errorf("open config source failed: %w", err) 59 | } 60 | 61 | var data []byte 62 | if decoder == nil { 63 | data, err = stripJSONComments(r) 64 | if err != nil { 65 | return fmt.Errorf("strip JSON comments failed: %w", err) 66 | } 67 | } else { 68 | data, err = io.ReadAll(r) 69 | if err != nil { 70 | return fmt.Errorf("read config data failed: %w", err) 71 | } 72 | data, err = encoding.Transform(data, decoder, json.Marshal) 73 | if err != nil { 74 | return fmt.Errorf("decode config failed: %w", err) 75 | } 76 | } 77 | 78 | if err := json.Unmarshal(data, c); err != nil { 79 | if decoder == nil { 80 | err = encoding.GetJSONSourceError(source, data, err) 81 | } else { 82 | switch e := err.(type) { 83 | case *json.UnmarshalTypeError: 84 | if e.Struct != "" || e.Field != "" { 85 | err = errors.New("cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String()) 86 | } else { 87 | err = errors.New("cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()) 88 | } 89 | } 90 | } 91 | return fmt.Errorf("unmarshal config failed: %w", err) 92 | } 93 | 94 | return nil 95 | } 96 | 97 | // loadFromHTTP loads the configuration from an HTTP source. 98 | // It handles redirects up to a maximum of 32 times. 99 | func (c *Config[T]) loadFromHTTP(source string, timeout time.Duration) (io.ReadCloser, error) { 100 | const maxRedirects = 32 101 | 102 | client := &http.Client{ 103 | Timeout: timeout, 104 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 105 | if len(via) >= maxRedirects { 106 | return errors.New("too many redirects") 107 | } 108 | return nil 109 | }, 110 | } 111 | 112 | resp, err := client.Get(source) 113 | if err != nil { 114 | return nil, fmt.Errorf("HTTP request failed: %w", err) 115 | } 116 | 117 | if resp.StatusCode != http.StatusOK { 118 | resp.Body.Close() 119 | return nil, fmt.Errorf("HTTP request failed with status code: %d", resp.StatusCode) 120 | } 121 | 122 | return resp.Body, nil 123 | } 124 | 125 | // processTemplate processes the UUID, Refs, and Options fields of each component.Config 126 | // as text/template templates, using c.Context as the template context. 127 | func (c *Config[T]) processTemplate(enableTemplate bool, source string) error { 128 | const option = "missingkey=error" 129 | for i := range c.Components { 130 | com := &c.Components[i] 131 | 132 | identifier := com.Name 133 | if com.UUID != "" { 134 | identifier += "#" + com.UUID 135 | } 136 | sourcePrefix := fmt.Sprintf("%s[%s].", source, identifier) 137 | if op.IfFunc(com.TemplateUUID == nil, enableTemplate, com.TemplateUUID.Deref) && com.UUID != "" { 138 | new, err := templates.Execute(sourcePrefix+"UUID", com.UUID, c.Context, option) 139 | if err != nil { 140 | return err 141 | } 142 | com.UUID = new 143 | } 144 | 145 | if op.IfFunc(com.TemplateRefs == nil, enableTemplate, com.TemplateRefs.Deref) && com.Refs.Len() > 0 { 146 | new, err := templates.Execute(sourcePrefix+"Refs", com.Refs.String(), c.Context, option) 147 | if err != nil { 148 | return err 149 | } 150 | com.Refs.SetString(new) 151 | } 152 | 153 | if op.IfFunc(com.TemplateOptions == nil, enableTemplate, com.TemplateOptions.Deref) && com.Options.Len() > 0 { 154 | new, err := templates.Execute(sourcePrefix+"Options", com.Options.String(), c.Context, option) 155 | if err != nil { 156 | return err 157 | } 158 | com.Options.SetString(new) 159 | } 160 | } 161 | 162 | return nil 163 | } 164 | 165 | // output encodes the configuration with the encoder and writes it to stdout. 166 | // It uses indentation for better readability. 167 | func (c *Config[T]) output(components []component.Config, stdout, stderr io.Writer, encoder encoding.Encoder) { 168 | if len(components) > 0 { 169 | c.Components = components 170 | } 171 | 172 | if encoder == nil { 173 | if data, err := jsonIndentEncoder(c); err != nil { 174 | fmt.Fprintf(stderr, "Encode config failed: %v\n", err) 175 | } else { 176 | fmt.Fprint(stdout, string(data)) 177 | } 178 | return 179 | } 180 | 181 | if data, err := json.Marshal(c); err != nil { 182 | fmt.Fprintf(stderr, "Encode config failed: %v\n", err) 183 | } else if data, err = encoding.Transform(data, json.Unmarshal, encoder); err != nil { 184 | fmt.Fprintf(stderr, "Encode config failed: %v\n", err) 185 | } else { 186 | fmt.Fprint(stdout, string(data)) 187 | } 188 | } 189 | 190 | func stripJSONComments(r io.Reader) ([]byte, error) { 191 | var buf bytes.Buffer 192 | scanner := bufio.NewScanner(r) 193 | for scanner.Scan() { 194 | line := scanner.Bytes() 195 | trimmed := bytes.TrimSpace(line) 196 | if !bytes.HasPrefix(trimmed, []byte("//")) { 197 | if _, err := buf.Write(line); err != nil { 198 | return nil, err 199 | } 200 | } 201 | if err := buf.WriteByte('\n'); err != nil { 202 | return nil, err 203 | } 204 | } 205 | if err := scanner.Err(); err != nil { 206 | return nil, err 207 | } 208 | // Remove the last newline if it exists 209 | bytes := buf.Bytes() 210 | if len(bytes) > 0 && bytes[len(bytes)-1] == '\n' { 211 | bytes = bytes[:len(bytes)-1] 212 | } 213 | return bytes, nil 214 | } 215 | 216 | func jsonIndentEncoder(v any) ([]byte, error) { 217 | var buf bytes.Buffer 218 | encoder := json.NewEncoder(&buf) 219 | encoder.SetEscapeHTML(false) 220 | encoder.SetIndent("", " ") 221 | 222 | if err := encoder.Encode(v); err != nil { 223 | return nil, err 224 | } 225 | return buf.Bytes(), nil 226 | } 227 | -------------------------------------------------------------------------------- /stringutil/stringutil.go: -------------------------------------------------------------------------------- 1 | package stringutil 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | ) 7 | 8 | // Capitalize returns the string with the first letter capitalized. 9 | func Capitalize(s string) string { 10 | if s == "" { 11 | return s 12 | } 13 | r := []rune(s) 14 | return string(unicode.ToUpper(r[0])) + string(r[1:]) 15 | } 16 | 17 | // Rename renames the string with the given convert function and separator. 18 | func Rename(s string, convert func(int, string) string, sep string) string { 19 | if s == "" { 20 | return "" 21 | } 22 | 23 | var result strings.Builder 24 | var word strings.Builder 25 | var count int 26 | 27 | runes := []rune(s) 28 | n := len(runes) 29 | i := 0 30 | 31 | for i < n { 32 | // Skip non-alphanumeric characters, treat them as word boundaries 33 | for i < n && !unicode.IsLetter(runes[i]) && !unicode.IsDigit(runes[i]) { 34 | i++ 35 | } 36 | 37 | if i >= n { 38 | break 39 | } 40 | 41 | word.Reset() 42 | 43 | // Collect characters for the current word 44 | for i < n && (unicode.IsLetter(runes[i]) || unicode.IsDigit(runes[i])) { 45 | r := runes[i] 46 | word.WriteRune(r) 47 | i++ 48 | 49 | if i < n && isWordBoundary(r, runes[i], i, runes) { 50 | break 51 | } 52 | } 53 | 54 | if word.Len() > 0 { 55 | if result.Len() > 0 && sep != "" { 56 | result.WriteString(sep) 57 | } 58 | 59 | convertedWord := convert(count, word.String()) 60 | result.WriteString(convertedWord) 61 | count++ 62 | } 63 | } 64 | 65 | return result.String() 66 | } 67 | 68 | func isWordBoundary(prev rune, curr rune, index int, runes []rune) bool { 69 | // If previous character is lowercase and current is uppercase, it's a boundary 70 | if unicode.IsLower(prev) && unicode.IsUpper(curr) { 71 | return true 72 | } 73 | 74 | // Handle acronyms (e.g., "HTTPServer") 75 | if unicode.IsUpper(prev) && unicode.IsUpper(curr) { 76 | // If next character exists and is lowercase, split before current character 77 | if index+1 < len(runes) && unicode.IsLower(runes[index+1]) { 78 | return true 79 | } 80 | return false 81 | } 82 | 83 | // If previous is digit and current is letter, it's a boundary 84 | if unicode.IsDigit(prev) && unicode.IsLetter(curr) { 85 | return true 86 | } 87 | 88 | // Do not split when transitioning from letter to digit 89 | if unicode.IsLetter(prev) && unicode.IsDigit(curr) { 90 | return false 91 | } 92 | 93 | // If both are letters (regardless of case), do not split 94 | if unicode.IsLetter(prev) && unicode.IsLetter(curr) { 95 | return false 96 | } 97 | 98 | // If both are digits, do not split 99 | if unicode.IsDigit(prev) && unicode.IsDigit(curr) { 100 | return false 101 | } 102 | 103 | // If one is letter/digit and the other is not, it's a boundary 104 | if (unicode.IsLetter(prev) || unicode.IsDigit(prev)) != (unicode.IsLetter(curr) || unicode.IsDigit(curr)) { 105 | return true 106 | } 107 | 108 | // Default to no boundary 109 | return false 110 | } 111 | 112 | func lowerAll(i int, s string) string { 113 | return strings.ToLower(s) 114 | } 115 | 116 | func capitalizeAll(i int, s string) string { 117 | return Capitalize(s) 118 | } 119 | 120 | func capitalizeExceptFirst(i int, s string) string { 121 | if i == 0 { 122 | return strings.ToLower(s) 123 | } 124 | return Capitalize(s) 125 | } 126 | 127 | // SnakeCase converts the string to snake_case. 128 | func SnakeCase(s string) string { 129 | return Rename(s, lowerAll, "_") 130 | } 131 | 132 | // KebabCase converts the string to kebab-case. 133 | func KebabCase(s string) string { 134 | return Rename(s, lowerAll, "-") 135 | } 136 | 137 | // CamelCase converts the string to camelCase. 138 | func CamelCase(s string) string { 139 | return Rename(s, capitalizeExceptFirst, "") 140 | } 141 | 142 | // PascalCase converts the string to PascalCase. 143 | func PascalCase(s string) string { 144 | return Rename(s, capitalizeAll, "") 145 | } 146 | -------------------------------------------------------------------------------- /stringutil/stringutil_test.go: -------------------------------------------------------------------------------- 1 | package stringutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCapitalize(t *testing.T) { 8 | tests := []struct { 9 | input string 10 | want string 11 | }{ 12 | {"", ""}, 13 | {"a", "A"}, 14 | {"A", "A"}, 15 | {"hello", "Hello"}, 16 | {"Hello", "Hello"}, 17 | {"1hello", "1hello"}, 18 | {" hello", " hello"}, 19 | {"hELLO", "HELLO"}, 20 | {"h", "H"}, 21 | } 22 | 23 | for _, tt := range tests { 24 | got := Capitalize(tt.input) 25 | if got != tt.want { 26 | t.Errorf("Capitalize(%q) = %q; want %q", tt.input, got, tt.want) 27 | } 28 | } 29 | } 30 | 31 | func TestSnakeCase(t *testing.T) { 32 | tests := []struct { 33 | input string 34 | want string 35 | }{ 36 | {"", ""}, 37 | {"simple", "simple"}, 38 | {"SimpleTestCase", "simple_test_case"}, 39 | {"HTTPServer", "http_server"}, 40 | {"xmlHTTPRequest", "xml_http_request"}, 41 | {"MyID", "my_id"}, 42 | {"My123ID", "my123_id"}, 43 | {"HelloWorld", "hello_world"}, 44 | {"helloWorld", "hello_world"}, 45 | {"hello_world", "hello_world"}, 46 | {"Hello_World", "hello_world"}, 47 | {"hello-world", "hello_world"}, 48 | } 49 | 50 | for _, tt := range tests { 51 | got := SnakeCase(tt.input) 52 | if got != tt.want { 53 | t.Errorf("SnakeCase(%q) = %q; want %q", tt.input, got, tt.want) 54 | } 55 | } 56 | } 57 | 58 | func TestKebabCase(t *testing.T) { 59 | tests := []struct { 60 | input string 61 | want string 62 | }{ 63 | {"", ""}, 64 | {"simple", "simple"}, 65 | {"SimpleTestCase", "simple-test-case"}, 66 | {"HTTPServer", "http-server"}, 67 | {"xmlHTTPRequest", "xml-http-request"}, 68 | {"MyID", "my-id"}, 69 | {"My123ID", "my123-id"}, 70 | {"HelloWorld", "hello-world"}, 71 | {"helloWorld", "hello-world"}, 72 | {"hello_world", "hello-world"}, 73 | {"Hello_World", "hello-world"}, 74 | } 75 | 76 | for _, tt := range tests { 77 | got := KebabCase(tt.input) 78 | if got != tt.want { 79 | t.Errorf("KebabCase(%q) = %q; want %q", tt.input, got, tt.want) 80 | } 81 | } 82 | } 83 | 84 | func TestCamelCase(t *testing.T) { 85 | tests := []struct { 86 | input string 87 | want string 88 | }{ 89 | {"", ""}, 90 | {"simple", "simple"}, 91 | {"SimpleTestCase", "simpleTestCase"}, 92 | {"HTTPServer", "httpServer"}, 93 | {"xmlHTTPRequest", "xmlHTTPRequest"}, 94 | {"MyID", "myID"}, 95 | {"My123ID", "my123ID"}, 96 | {"HelloWorld", "helloWorld"}, 97 | {"helloWorld", "helloWorld"}, 98 | {"hello_world", "helloWorld"}, 99 | {"Hello_World", "helloWorld"}, 100 | {"hello world", "helloWorld"}, 101 | {"hello World", "helloWorld"}, 102 | {"Hello World", "helloWorld"}, 103 | {"hello-world", "helloWorld"}, 104 | {"hello-World", "helloWorld"}, 105 | {"Hello-World", "helloWorld"}, 106 | } 107 | 108 | for _, tt := range tests { 109 | got := CamelCase(tt.input) 110 | if got != tt.want { 111 | t.Errorf("CamelCase(%q) = %q; want %q", tt.input, got, tt.want) 112 | } 113 | } 114 | } 115 | 116 | func TestPascalCase(t *testing.T) { 117 | tests := []struct { 118 | input string 119 | want string 120 | }{ 121 | {"", ""}, 122 | {"simple", "Simple"}, 123 | {"SimpleTestCase", "SimpleTestCase"}, 124 | {"HTTPServer", "HTTPServer"}, 125 | {"xmlHTTPRequest", "XmlHTTPRequest"}, 126 | {"MyID", "MyID"}, 127 | {"My123ID", "My123ID"}, 128 | {"HelloWorld", "HelloWorld"}, 129 | {"helloWorld", "HelloWorld"}, 130 | {"hello_world", "HelloWorld"}, 131 | {"Hello_World", "HelloWorld"}, 132 | } 133 | 134 | for _, tt := range tests { 135 | got := PascalCase(tt.input) 136 | if got != tt.want { 137 | t.Errorf("PascalCase(%q) = %q; want %q", tt.input, got, tt.want) 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /term/term.go: -------------------------------------------------------------------------------- 1 | // Package term provides terminal-related utilities. 2 | package term 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "os" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | isSupportsAnsi = func() bool { 14 | return runtime.GOOS != "windows" || os.Getenv("TERM") != "" 15 | }() 16 | isSupports256Colors = func() bool { 17 | term := os.Getenv("TERM") 18 | if strings.Contains(term, "256color") { 19 | return true 20 | } 21 | if strings.Contains(term, "xterm") || strings.Contains(term, "screen") || strings.Contains(term, "tmux") || strings.Contains(term, "rxvt") { 22 | return true 23 | } 24 | colorterm := os.Getenv("COLORTERM") 25 | if strings.Contains(strings.ToLower(colorterm), "truecolor") || strings.Contains(strings.ToLower(colorterm), "24bit") { 26 | return true 27 | } 28 | return false 29 | }() 30 | ) 31 | 32 | // IsTerminal reports whether w is a terminal. 33 | func IsTerminal(w io.Writer) bool { 34 | if w, ok := w.(interface{ IsTerminal() bool }); ok { 35 | return w.IsTerminal() 36 | } 37 | f, ok := w.(*os.File) 38 | if !ok { 39 | return false 40 | } 41 | stat, err := f.Stat() 42 | if err != nil { 43 | return false 44 | } 45 | return (stat.Mode() & os.ModeCharDevice) != 0 46 | } 47 | 48 | // IsSupportsAnsi reports whether the terminal supports ANSI escape codes. 49 | func IsSupportsAnsi() bool { 50 | return isSupportsAnsi 51 | } 52 | 53 | // IsSupports256Colors reports whether the terminal supports 256 colors. 54 | func IsSupports256Colors() bool { 55 | return isSupports256Colors 56 | } 57 | 58 | // ColorizeWriter returns a colorized writer if w is a terminal and supports ANSI escape codes. 59 | func ColorizeWriter(w io.Writer, c Color) io.Writer { 60 | if IsTerminal(w) && isSupportsAnsi && (!c.Is256() || isSupports256Colors) { 61 | return &colorizeWriter{w: w, c: c} 62 | } 63 | return w 64 | } 65 | 66 | type colorizeWriter struct { 67 | w io.Writer 68 | c Color 69 | } 70 | 71 | func (w *colorizeWriter) Write(p []byte) (n int, err error) { 72 | return w.w.Write([]byte(w.c.Format(string(p)))) 73 | } 74 | 75 | // Color represents a terminal color. 76 | type Color string 77 | 78 | // Is256 reports whether the color is a 256 color. 79 | func (c Color) Is256() bool { 80 | return strings.HasPrefix(string(c), "\033[38;5;") 81 | } 82 | 83 | // Colorize returns a colorized string. 84 | func (c Color) Colorize(s string) fmt.Stringer { 85 | return colorizedString{value: s, color: c} 86 | } 87 | 88 | // Background returns the background version of the color 89 | func (c Color) Background() Color { 90 | if len(c) < 6 { 91 | return c 92 | } 93 | bg := string(c[:5]) + "4" + string(c[6:]) 94 | return Color(bg) 95 | } 96 | 97 | // Format formats the string s with the color c. 98 | func (c Color) Format(s string) string { 99 | if c == "" { 100 | return s 101 | } 102 | return string(c) + s + Reset 103 | } 104 | 105 | // Fprint formats using the default formats for its operands and writes to w. 106 | func Fprint(w io.Writer, a ...any) (n int, err error) { 107 | isTerminal := IsTerminal(w) 108 | if isTerminal && isSupports256Colors { 109 | return fmt.Fprint(w, a...) 110 | } 111 | return fmt.Fprint(w, removeColors(isTerminal, a)...) 112 | } 113 | 114 | // Fprintf formats according to a format specifier and writes to w. 115 | func Fprintf(w io.Writer, format string, a ...any) (n int, err error) { 116 | isTerminal := IsTerminal(w) 117 | if isTerminal && isSupports256Colors { 118 | return fmt.Fprintf(w, format, a...) 119 | } 120 | return fmt.Fprintf(w, format, removeColors(isTerminal, a)...) 121 | } 122 | 123 | // Fprintln formats using the default formats for its operands and writes to w. 124 | func Fprintln(w io.Writer, a ...any) (n int, err error) { 125 | isTerminal := IsTerminal(w) 126 | if isTerminal && isSupports256Colors { 127 | return fmt.Fprintln(w, a...) 128 | } 129 | return fmt.Fprintln(w, removeColors(isTerminal, a)...) 130 | } 131 | 132 | type colorizedString struct { 133 | value string 134 | color Color 135 | } 136 | 137 | // String implements fmt.Stringer. 138 | func (s colorizedString) String() string { 139 | return s.color.Format(s.value) 140 | } 141 | 142 | func getColorizedString(a any) *colorizedString { 143 | if s, ok := a.(colorizedString); ok { 144 | return &s 145 | } 146 | if s, ok := a.(*colorizedString); ok && s != nil { 147 | return s 148 | } 149 | return nil 150 | } 151 | 152 | func removeColors(isTerminal bool, a []any) []any { 153 | for _, arg := range a { 154 | s := getColorizedString(arg) 155 | if s == nil || (isTerminal && isSupportsAnsi && !s.color.Is256()) { 156 | continue 157 | } 158 | args := make([]any, len(a)) 159 | for i := range a { 160 | s := getColorizedString(a[i]) 161 | if s != nil && (!isTerminal || !isSupportsAnsi || s.color.Is256()) { 162 | args[i] = s.value 163 | } else { 164 | args[i] = a[i] 165 | } 166 | } 167 | return args 168 | } 169 | return a 170 | } 171 | -------------------------------------------------------------------------------- /text/document/document.go: -------------------------------------------------------------------------------- 1 | // Package document provides efficient document indexing and updating 2 | // capabilities for text editors and language servers. 3 | package document 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "net/url" 9 | "path/filepath" 10 | "runtime" 11 | "sort" 12 | "strings" 13 | "unicode/utf8" 14 | ) 15 | 16 | var ( 17 | // ErrInvalidLine indicates an invalid line number. 18 | ErrInvalidLine = errors.New("invalid line number") 19 | 20 | // ErrInvalidCharacter indicates an invalid character offset. 21 | ErrInvalidCharacter = errors.New("invalid character offset") 22 | 23 | // ErrInvalidOffset indicates an invalid byte offset. 24 | ErrInvalidOffset = errors.New("invalid offset") 25 | ) 26 | 27 | // Position represents a position in a text document. 28 | type Position struct { 29 | Line int // Line number (0-based) 30 | Character int // Character offset in line (0-based) 31 | } 32 | 33 | // IsValid returns true if the position is valid. 34 | func (p Position) IsValid() bool { 35 | return p.Line >= 0 && p.Character >= 0 36 | } 37 | 38 | // IsBefore returns true if the position is before the other position. 39 | func (p Position) IsBefore(other Position) bool { 40 | if p.Line < other.Line { 41 | return true 42 | } 43 | if p.Line > other.Line { 44 | return false 45 | } 46 | return p.Character < other.Character 47 | } 48 | 49 | // FormatPosition formats a position as a string. 50 | func FormatPosition(filename string, p Position) string { 51 | if !p.IsValid() { 52 | return filename 53 | } 54 | if filename != "" { 55 | return fmt.Sprintf("%s:%d:%d", filename, p.Line+1, p.Character+1) 56 | } 57 | return fmt.Sprintf("%d:%d", p.Line+1, p.Character+1) 58 | } 59 | 60 | // Range represents a range in a text document. 61 | type Range struct { 62 | Start Position 63 | End Position 64 | } 65 | 66 | // ChangeEvent represents a change in a text document. 67 | type ChangeEvent struct { 68 | Range *Range // nil for full document updates 69 | Text string 70 | } 71 | 72 | // Document represents an indexed text document. 73 | type Document struct { 74 | uri string 75 | path string 76 | content string 77 | index *index 78 | } 79 | 80 | type index struct { 81 | lines []lineInfo 82 | } 83 | 84 | type lineInfo struct { 85 | ByteOffset int 86 | RuneOffset int 87 | RunesInLine int 88 | } 89 | 90 | // NewDocument creates a new Document with the given URI and content. 91 | func NewDocument(uri, content string) *Document { 92 | doc := &Document{ 93 | uri: uri, 94 | path: URIToPath(uri), 95 | content: content, 96 | } 97 | 98 | doc.index = buildIndex(content) 99 | return doc 100 | } 101 | 102 | // URIToPath converts a URI to a file path. 103 | func URIToPath(uri string) string { 104 | parsedURI, err := url.Parse(uri) 105 | if err != nil { 106 | return uri 107 | } 108 | 109 | path := parsedURI.Path 110 | 111 | // Handle Windows paths 112 | if runtime.GOOS == "windows" { 113 | // If the path starts with a slash followed by a drive letter 114 | if strings.HasPrefix(path, "/") && len(path) > 2 && path[2] == ':' { 115 | path = path[1:] // Remove the leading slash 116 | } 117 | } 118 | 119 | return filepath.FromSlash(path) 120 | } 121 | 122 | func buildIndex(content string) *index { 123 | lines := []lineInfo{{ByteOffset: 0, RuneOffset: 0, RunesInLine: 0}} 124 | runeCount := 0 125 | 126 | for byteOffset, r := range content { 127 | if r == '\n' { 128 | lines = append(lines, lineInfo{ 129 | ByteOffset: byteOffset + 1, 130 | RuneOffset: runeCount + 1, 131 | RunesInLine: 0, 132 | }) 133 | } else { 134 | lines[len(lines)-1].RunesInLine++ 135 | } 136 | runeCount++ 137 | } 138 | 139 | return &index{lines: lines} 140 | } 141 | 142 | // URI returns the URI of the document. 143 | func (d *Document) URI() string { 144 | return d.uri 145 | } 146 | 147 | // Path returns the file path of the document. 148 | func (d *Document) Path() string { 149 | return d.path 150 | } 151 | 152 | // Filename returns the base name of the document path. 153 | func (d *Document) Filename() string { 154 | return filepath.Base(d.path) 155 | } 156 | 157 | // Extension returns the file extension of the document path. 158 | func (d *Document) Extension() string { 159 | return filepath.Ext(d.path) 160 | } 161 | 162 | // Content returns the content of the document. 163 | func (d *Document) Content() string { 164 | return d.content 165 | } 166 | 167 | // ApplyChanges applies the given changes to the document. 168 | func (d *Document) ApplyChanges(changes []ChangeEvent) error { 169 | for _, change := range changes { 170 | if change.Range == nil { 171 | // Full document update 172 | d.content = change.Text 173 | d.index = buildIndex(d.content) 174 | continue 175 | } 176 | 177 | startOffset, err := d.PositionToOffset(change.Range.Start) 178 | if err != nil { 179 | return err 180 | } 181 | endOffset, err := d.PositionToOffset(change.Range.End) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | beforeChange := d.content[:startOffset] 187 | afterChange := d.content[endOffset:] 188 | d.content = beforeChange + change.Text + afterChange 189 | 190 | d.updateIndex(change.Range.Start.Line, startOffset, len(beforeChange)+len(change.Text)) 191 | } 192 | return nil 193 | } 194 | 195 | func (d *Document) updateIndex(startLine, startByteOffset, endByteOffset int) { 196 | newContent := d.content[startByteOffset:endByteOffset] 197 | newLines := strings.Count(newContent, "\n") 198 | 199 | if newLines == 0 { 200 | d.updateSingleLine(startLine, startByteOffset, endByteOffset) 201 | } else { 202 | d.replaceLines(startLine, startByteOffset, endByteOffset, newLines+1) 203 | } 204 | } 205 | 206 | func (d *Document) updateSingleLine(line, startByteOffset, endByteOffset int) { 207 | oldRuneCount := d.index.lines[line].RunesInLine 208 | newRuneCount := utf8.RuneCountInString(d.content[d.index.lines[line].ByteOffset:endByteOffset]) 209 | runesDiff := newRuneCount - oldRuneCount 210 | bytesDiff := endByteOffset - startByteOffset 211 | 212 | d.index.lines[line].RunesInLine = newRuneCount 213 | for i := line + 1; i < len(d.index.lines); i++ { 214 | d.index.lines[i].ByteOffset += bytesDiff 215 | d.index.lines[i].RuneOffset += runesDiff 216 | } 217 | } 218 | 219 | func (d *Document) replaceLines(startLine, startByteOffset, endByteOffset, newLineCount int) { 220 | oldLineCount := 1 221 | for i := startLine + 1; i < len(d.index.lines) && d.index.lines[i].ByteOffset <= endByteOffset; i++ { 222 | oldLineCount++ 223 | } 224 | 225 | newLines := make([]lineInfo, newLineCount) 226 | newLines[0] = d.index.lines[startLine] 227 | 228 | runeOffset := newLines[0].RuneOffset 229 | byteOffset := startByteOffset 230 | 231 | for i := 1; i < newLineCount; i++ { 232 | nlIndex := strings.IndexByte(d.content[byteOffset:endByteOffset], '\n') 233 | if nlIndex == -1 { 234 | break 235 | } 236 | runeOffset += utf8.RuneCountInString(d.content[byteOffset : byteOffset+nlIndex+1]) 237 | byteOffset += nlIndex + 1 238 | 239 | newLines[i] = lineInfo{ 240 | ByteOffset: byteOffset, 241 | RuneOffset: runeOffset, 242 | RunesInLine: 0, // Will be set later 243 | } 244 | } 245 | 246 | // Set RunesInLine for all new lines 247 | for i := 0; i < newLineCount-1; i++ { 248 | newLines[i].RunesInLine = newLines[i+1].RuneOffset - newLines[i].RuneOffset - 1 // -1 for newline character 249 | } 250 | newLines[newLineCount-1].RunesInLine = utf8.RuneCountInString(d.content[newLines[newLineCount-1].ByteOffset:endByteOffset]) 251 | 252 | // Replace old lines with new lines 253 | d.index.lines = append(d.index.lines[:startLine], append(newLines, d.index.lines[startLine+oldLineCount:]...)...) 254 | 255 | // Update subsequent lines 256 | runesDiff := newLines[newLineCount-1].RuneOffset + newLines[newLineCount-1].RunesInLine - 257 | (d.index.lines[startLine].RuneOffset + utf8.RuneCountInString(d.content[startByteOffset:endByteOffset])) 258 | bytesDiff := endByteOffset - startByteOffset 259 | 260 | for i := startLine + newLineCount; i < len(d.index.lines); i++ { 261 | d.index.lines[i].ByteOffset += bytesDiff 262 | d.index.lines[i].RuneOffset += runesDiff 263 | } 264 | } 265 | 266 | // LineCount returns the number of lines in the document. 267 | func (d *Document) LineCount() int { 268 | return len(d.index.lines) 269 | } 270 | 271 | // RuneCount returns the number of runes in the document. 272 | func (d *Document) RuneCount() int { 273 | if len(d.index.lines) == 0 { 274 | return 0 275 | } 276 | lastLine := d.index.lines[len(d.index.lines)-1] 277 | return lastLine.RuneOffset + lastLine.RunesInLine 278 | } 279 | 280 | // PositionToOffset converts a Position to a byte offset in the document. 281 | func (d *Document) PositionToOffset(pos Position) (int, error) { 282 | if pos.Line < 0 || pos.Line >= len(d.index.lines) { 283 | return 0, ErrInvalidLine 284 | } 285 | 286 | lineInfo := d.index.lines[pos.Line] 287 | if pos.Character < 0 || pos.Character > lineInfo.RunesInLine { 288 | return 0, ErrInvalidCharacter 289 | } 290 | 291 | offset := lineInfo.ByteOffset 292 | for i := 0; i < pos.Character; i++ { 293 | _, size := utf8.DecodeRuneInString(d.content[offset:]) 294 | offset += size 295 | } 296 | 297 | return offset, nil 298 | } 299 | 300 | // OffsetToPosition converts a byte offset to a Position in the document. 301 | func (d *Document) OffsetToPosition(offset int) (Position, error) { 302 | if offset < 0 || offset > len(d.content) { 303 | return Position{}, ErrInvalidOffset 304 | } 305 | 306 | line := sort.Search(len(d.index.lines), func(i int) bool { 307 | return d.index.lines[i].ByteOffset > offset 308 | }) - 1 309 | 310 | if line < 0 { 311 | line = 0 312 | } 313 | 314 | lineInfo := d.index.lines[line] 315 | char := utf8.RuneCountInString(d.content[lineInfo.ByteOffset:offset]) 316 | 317 | return Position{Line: line, Character: char}, nil 318 | } 319 | 320 | // PositionFor returns a Position for the given byte offset in the content. 321 | func PositionFor(content string, offset int) Position { 322 | if offset < 0 || offset >= len(content) { 323 | return Position{} 324 | } 325 | text := content[:offset] 326 | col := strings.LastIndex(text, "\n") 327 | if col == -1 { 328 | col = utf8.RuneCountInString(text) 329 | } else { 330 | col++ // After the newline. 331 | col = utf8.RuneCountInString(text[col:]) 332 | } 333 | line := strings.Count(text, "\n") 334 | return Position{Line: line, Character: col} 335 | } 336 | -------------------------------------------------------------------------------- /text/templates/templates.go: -------------------------------------------------------------------------------- 1 | // Package templates provides utility functions for working with Go templates. 2 | package templates 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "regexp" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | // New creates a new template with the default functions. 13 | func New(name string) *template.Template { 14 | return template.New(name).Funcs(Funcs) 15 | } 16 | 17 | // Execute executes the default template with the given text and data. 18 | func Execute(name, content string, data any, options ...string) (string, error) { 19 | var buf bytes.Buffer 20 | t := New(name) 21 | if len(options) > 0 { 22 | t = t.Option(options...) 23 | } 24 | if t, err := t.Parse(content); err != nil { 25 | return "", err 26 | } else if err := t.Execute(&buf, data); err != nil { 27 | return "", cleanTemplateError(err) 28 | } else { 29 | return buf.String(), nil 30 | } 31 | } 32 | 33 | func cleanTemplateError(err error) error { 34 | if err == nil { 35 | return nil 36 | } 37 | errMsg := err.Error() 38 | re := regexp.MustCompile(`^(template: .*?:\d+:\d+): executing "(.*?)" at .*?: (.*)$`) 39 | matches := re.FindStringSubmatch(errMsg) 40 | if len(matches) == 4 && strings.Contains(matches[1], matches[2]) { 41 | cleaned := matches[1] + ": " + matches[3] 42 | return errors.New(cleaned) 43 | } 44 | return err 45 | } 46 | -------------------------------------------------------------------------------- /text/templates/types.go: -------------------------------------------------------------------------------- 1 | package templates 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "slices" 7 | ) 8 | 9 | // @api(Container/Vector) represents a slice of any type. Function `list` creates a Vector from the given values. 10 | // 11 | // Example: 12 | // ```tmpl 13 | // {{list 1 2 3}} 14 | // ``` 15 | // 16 | // Output: 17 | // ``` 18 | // [1 2 3] 19 | // ``` 20 | type Vector []any 21 | 22 | // @api(Container/Vector.Push) pushes an item to the end of the vector. 23 | // 24 | // Example: 25 | // ```tmpl 26 | // {{- $v := list 1 2 3}} 27 | // {{$v.Push 4}} 28 | // ``` 29 | // 30 | // Output: 31 | // ``` 32 | // [1 2 3 4] 33 | // ``` 34 | func (v *Vector) Push(item any) Vector { 35 | *v = append(*v, item) 36 | return *v 37 | } 38 | 39 | // @api(Container/Vector.Pop) pops an item from the end of the vector. 40 | // It returns an error if the vector is empty. 41 | // 42 | // Example: 43 | // ```tmpl 44 | // {{- $v := list 1 2 3}} 45 | // {{- $v.Pop}} 46 | // {{$v}} 47 | // ``` 48 | // 49 | // Output: 50 | // ``` 51 | // 3 52 | // [1 2] 53 | // ``` 54 | func (v *Vector) Pop() (any, error) { 55 | if len(*v) == 0 { 56 | return nil, errors.New("empty vector") 57 | } 58 | item := (*v)[len(*v)-1] 59 | *v = (*v)[:len(*v)-1] 60 | return item, nil 61 | } 62 | 63 | // @api(Container/Vector.Len) returns the length of the vector. 64 | // 65 | // Example: 66 | // ```tmpl 67 | // {{- $v := list 1 2 3 4 5}} 68 | // {{- $v.Len}} 69 | // ``` 70 | // 71 | // Output: 72 | // ``` 73 | // 5 74 | // ``` 75 | func (v Vector) Len() int { 76 | return len(v) 77 | } 78 | 79 | // @api(Container/Vector.IsEmpty) reports whether the vector is empty. 80 | // 81 | // Example: 82 | // ```tmpl 83 | // {{- $v := list 1 2}} 84 | // {{- $v.IsEmpty}} 85 | // {{- $v.Pop | _ }} 86 | // {{- $v.IsEmpty}} 87 | // {{- $v.Pop | _ }} 88 | // {{- $v.IsEmpty}} 89 | // ``` 90 | // 91 | // Output: 92 | // ``` 93 | // false 94 | // false 95 | // true 96 | // ``` 97 | func (v Vector) IsEmpty() bool { 98 | return len(v) == 0 99 | } 100 | 101 | // @api(Container/Vector.Get) returns the item at the specified index. 102 | func (v Vector) Get(index int) (any, error) { 103 | if index < 0 || index >= len(v) { 104 | return nil, fmt.Errorf("index %d out of range [0, %d)", index, len(v)) 105 | } 106 | return v[index], nil 107 | } 108 | 109 | // @api(Container/Vector.Set) sets the item at the specified index. 110 | // It returns an error if the index is out of range. 111 | func (v Vector) Set(index int, item any) error { 112 | if index < 0 || index >= len(v) { 113 | return fmt.Errorf("index %d out of range [0, %d)", index, len(v)) 114 | } 115 | v[index] = item 116 | return nil 117 | } 118 | 119 | // @api(Container/Vector.Insert) inserts an item at the specified index. 120 | func (v *Vector) Insert(index int, item any) error { 121 | if index < 0 || index > len(*v) { 122 | return fmt.Errorf("index %d out of range [0, %d]", index, len(*v)) 123 | } 124 | *v = slices.Insert(*v, index, item) 125 | return nil 126 | } 127 | 128 | // @api(Container/Vector.RemoveAt) removes the item at the specified index. 129 | func (v *Vector) RemoveAt(index int) error { 130 | if index < 0 || index >= len(*v) { 131 | return fmt.Errorf("index %d out of range [0, %d)", index, len(*v)) 132 | } 133 | *v = append((*v)[:index], (*v)[index+1:]...) 134 | return nil 135 | } 136 | 137 | // @api(Container/Vector.Splice) removes count items starting from the specified index and inserts new items. 138 | func (v *Vector) Splice(start, count int, items ...any) error { 139 | if start < 0 || start >= len(*v) { 140 | return fmt.Errorf("start index %d out of range [0, %d)", start, len(*v)) 141 | } 142 | if count < 0 || start+count > len(*v) { 143 | return fmt.Errorf("count %d out of range [0, %d)", count, len(*v)-start) 144 | } 145 | *v = append((*v)[:start], append(items, (*v)[start+count:]...)...) 146 | return nil 147 | } 148 | 149 | // @api(Container/Vector.Clear) removes all items from the vector. 150 | func (v *Vector) Clear() { 151 | *v = nil 152 | } 153 | 154 | // @api(Container/Dictionary) represents a dictionary of any key-value pairs. 155 | type Dictionary map[any]any 156 | 157 | // @api(Container/Dictionary.Get) returns the value for the specified key. 158 | func (d Dictionary) Get(key any) any { 159 | return d[key] 160 | } 161 | 162 | // @api(Container/Dictionary.Set) sets the value for the specified key. 163 | func (d Dictionary) Set(key, value any) { 164 | d[key] = value 165 | } 166 | 167 | // @api(Container/Dictionary.Has) reports whether the dictionary has the specified key. 168 | func (d Dictionary) Has(key any) bool { 169 | _, ok := d[key] 170 | return ok 171 | } 172 | 173 | // @api(Container/Dictionary.Remove) removes the specified key from the dictionary. 174 | func (d Dictionary) Remove(key any) { 175 | delete(d, key) 176 | } 177 | 178 | // @api(Container/Dictionary.Clear) removes all key-value pairs from the dictionary. 179 | func (d Dictionary) Clear() { 180 | for key := range d { 181 | delete(d, key) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /text/text.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | // ContainsWord returns true if the given word is found in the string s. 9 | func ContainsWord(s, word string) bool { 10 | pattern := fmt.Sprintf(`\b%s\b`, regexp.QuoteMeta(word)) 11 | matched, _ := regexp.MatchString(pattern, s) 12 | return matched 13 | } 14 | --------------------------------------------------------------------------------