├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cover.out ├── dagger.go ├── dagger_test.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | tmp 3 | testing.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | .PHONY: help 4 | help: 5 | @echo "Makefile Commands:" 6 | @echo "----------------------------------------------------------------" 7 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' 8 | @echo "----------------------------------------------------------------" 9 | 10 | test: ## run unit tests 11 | go test -cover -v -coverprofile cover.out -race . 12 | 13 | coverage: ## show test coverage 14 | go tool cover -func cover.out 15 | 16 | docs: 17 | @echo "Generating docs..." 18 | @gomarkdoc --output README.md . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # dagger 4 | 5 | ```go 6 | import "github.com/autom8ter/dagger/v3" 7 | ``` 8 | 9 | Package dagger is a collection of generic, concurrency safe datastructures including a Directed Acyclic Graph and others. Datastructures are implemented using generics in Go 1.18. 10 | 11 | Supported Datastructures: 12 | 13 | DAG: thread safe directed acyclic graph 14 | 15 | Queue: unbounded thread safe fifo queue 16 | 17 | Stack: unbounded thread safe lifo stack 18 | 19 | BoundedQueue: bounded thread safe fifo queue with a fixed capacity 20 | 21 | PriorityQueue: thread safe priority queue 22 | 23 | HashMap: thread safe hashmap 24 | 25 | Set: thread safe set 26 | 27 | ChannelGroup: thread safe group of channels for broadcasting 1 value to N channels 28 | 29 | MultiContext: thread safe context for coordinating the cancellation of multiple contexts 30 | 31 | Borrower: thread safe object ownership manager 32 | 33 | ## Index 34 | 35 | - [func UniqueID\(prefix string\) string](<#UniqueID>) 36 | - [type BoundedQueue](<#BoundedQueue>) 37 | - [func NewBoundedQueue\[T any\]\(maxSize int\) \*BoundedQueue\[T\]](<#NewBoundedQueue>) 38 | - [func \(q \*BoundedQueue\[T\]\) Close\(\)](<#BoundedQueue[T].Close>) 39 | - [func \(q \*BoundedQueue\[T\]\) Len\(\) int](<#BoundedQueue[T].Len>) 40 | - [func \(q \*BoundedQueue\[T\]\) Pop\(\) \(T, bool\)](<#BoundedQueue[T].Pop>) 41 | - [func \(q \*BoundedQueue\[T\]\) PopContext\(ctx context.Context\) \(T, bool\)](<#BoundedQueue[T].PopContext>) 42 | - [func \(q \*BoundedQueue\[T\]\) Push\(val T\) bool](<#BoundedQueue[T].Push>) 43 | - [func \(q \*BoundedQueue\[T\]\) PushContext\(ctx context.Context, val T\) bool](<#BoundedQueue[T].PushContext>) 44 | - [func \(q \*BoundedQueue\[T\]\) Range\(fn func\(element T\) bool\)](<#BoundedQueue[T].Range>) 45 | - [func \(q \*BoundedQueue\[T\]\) RangeContext\(ctx context.Context, fn func\(element T\) bool\)](<#BoundedQueue[T].RangeContext>) 46 | - [type DAG](<#DAG>) 47 | - [func NewDAG\[T Node\]\(opts ...DagOpt\) \(\*DAG\[T\], error\)](<#NewDAG>) 48 | - [func \(g \*DAG\[T\]\) Acyclic\(\) bool](<#DAG[T].Acyclic>) 49 | - [func \(g \*DAG\[T\]\) BFS\(ctx context.Context, reverse bool, start \*GraphNode\[T\], search GraphSearchFunc\[T\]\) error](<#DAG[T].BFS>) 50 | - [func \(g \*DAG\[T\]\) DFS\(ctx context.Context, reverse bool, start \*GraphNode\[T\], fn GraphSearchFunc\[T\]\) error](<#DAG[T].DFS>) 51 | - [func \(g \*DAG\[T\]\) GetEdge\(id string\) \(\*GraphEdge\[T\], bool\)](<#DAG[T].GetEdge>) 52 | - [func \(g \*DAG\[T\]\) GetEdges\(\) \[\]\*GraphEdge\[T\]](<#DAG[T].GetEdges>) 53 | - [func \(g \*DAG\[T\]\) GetNode\(id string\) \(\*GraphNode\[T\], bool\)](<#DAG[T].GetNode>) 54 | - [func \(g \*DAG\[T\]\) GetNodes\(\) \[\]\*GraphNode\[T\]](<#DAG[T].GetNodes>) 55 | - [func \(g \*DAG\[T\]\) GraphViz\(\) \(image.Image, error\)](<#DAG[T].GraphViz>) 56 | - [func \(g \*DAG\[T\]\) HasEdge\(id string\) bool](<#DAG[T].HasEdge>) 57 | - [func \(g \*DAG\[T\]\) HasNode\(id string\) bool](<#DAG[T].HasNode>) 58 | - [func \(g \*DAG\[T\]\) RangeEdges\(fn func\(e \*GraphEdge\[T\]\) bool\)](<#DAG[T].RangeEdges>) 59 | - [func \(g \*DAG\[T\]\) RangeNodes\(fn func\(n \*GraphNode\[T\]\) bool\)](<#DAG[T].RangeNodes>) 60 | - [func \(g \*DAG\[T\]\) SetNode\(node Node\) \*GraphNode\[T\]](<#DAG[T].SetNode>) 61 | - [func \(g \*DAG\[T\]\) Size\(\) \(int, int\)](<#DAG[T].Size>) 62 | - [func \(g \*DAG\[T\]\) TopologicalSort\(reverse bool\) \(\[\]\*GraphNode\[T\], error\)](<#DAG[T].TopologicalSort>) 63 | - [type DagOpt](<#DagOpt>) 64 | - [func WithVizualization\(\) DagOpt](<#WithVizualization>) 65 | - [type GraphEdge](<#GraphEdge>) 66 | - [func \(n \*GraphEdge\[T\]\) From\(\) \*GraphNode\[T\]](<#GraphEdge[T].From>) 67 | - [func \(n \*GraphEdge\[T\]\) ID\(\) string](<#GraphEdge[T].ID>) 68 | - [func \(n \*GraphEdge\[T\]\) Metadata\(\) map\[string\]string](<#GraphEdge[T].Metadata>) 69 | - [func \(n \*GraphEdge\[T\]\) Relationship\(\) string](<#GraphEdge[T].Relationship>) 70 | - [func \(n \*GraphEdge\[T\]\) SetMetadata\(metadata map\[string\]string\)](<#GraphEdge[T].SetMetadata>) 71 | - [func \(n \*GraphEdge\[T\]\) To\(\) \*GraphNode\[T\]](<#GraphEdge[T].To>) 72 | - [type GraphNode](<#GraphNode>) 73 | - [func \(n \*GraphNode\[T\]\) Ancestors\(fn func\(node \*GraphNode\[T\]\) bool\)](<#GraphNode[T].Ancestors>) 74 | - [func \(n \*GraphNode\[T\]\) BFS\(ctx context.Context, reverse bool, fn GraphSearchFunc\[T\]\) error](<#GraphNode[T].BFS>) 75 | - [func \(n \*GraphNode\[T\]\) DFS\(ctx context.Context, reverse bool, fn GraphSearchFunc\[T\]\) error](<#GraphNode[T].DFS>) 76 | - [func \(n \*GraphNode\[T\]\) Descendants\(fn func\(node \*GraphNode\[T\]\) bool\)](<#GraphNode[T].Descendants>) 77 | - [func \(n \*GraphNode\[T\]\) EdgesFrom\(relationship string, fn func\(e \*GraphEdge\[T\]\) bool\)](<#GraphNode[T].EdgesFrom>) 78 | - [func \(n \*GraphNode\[T\]\) EdgesTo\(relationship string, fn func\(e \*GraphEdge\[T\]\) bool\)](<#GraphNode[T].EdgesTo>) 79 | - [func \(n \*GraphNode\[T\]\) Graph\(\) \*DAG\[T\]](<#GraphNode[T].Graph>) 80 | - [func \(n \*GraphNode\[T\]\) IsConnectedTo\(node \*GraphNode\[T\]\) bool](<#GraphNode[T].IsConnectedTo>) 81 | - [func \(n \*GraphNode\[T\]\) Remove\(\) error](<#GraphNode[T].Remove>) 82 | - [func \(n \*GraphNode\[T\]\) RemoveEdge\(edgeID string\)](<#GraphNode[T].RemoveEdge>) 83 | - [func \(n \*GraphNode\[T\]\) SetEdge\(relationship string, toNode Node, metadata map\[string\]string\) \(\*GraphEdge\[T\], error\)](<#GraphNode[T].SetEdge>) 84 | - [type GraphSearchFunc](<#GraphSearchFunc>) 85 | - [type HashMap](<#HashMap>) 86 | - [func NewHashMap\[K comparable, V any\]\(\) \*HashMap\[K, V\]](<#NewHashMap>) 87 | - [func \(n \*HashMap\[K, V\]\) Clear\(\)](<#HashMap[K, V].Clear>) 88 | - [func \(n \*HashMap\[K, V\]\) Delete\(key K\)](<#HashMap[K, V].Delete>) 89 | - [func \(n \*HashMap\[K, V\]\) Exists\(key K\) bool](<#HashMap[K, V].Exists>) 90 | - [func \(n \*HashMap\[K, V\]\) Filter\(f func\(key K, value V\) bool\) \*HashMap\[K, V\]](<#HashMap[K, V].Filter>) 91 | - [func \(n \*HashMap\[K, V\]\) Get\(key K\) \(V, bool\)](<#HashMap[K, V].Get>) 92 | - [func \(n \*HashMap\[K, V\]\) Keys\(\) \[\]K](<#HashMap[K, V].Keys>) 93 | - [func \(n \*HashMap\[K, V\]\) Len\(\) int](<#HashMap[K, V].Len>) 94 | - [func \(n \*HashMap\[K, V\]\) Map\(\) map\[K\]V](<#HashMap[K, V].Map>) 95 | - [func \(n \*HashMap\[K, V\]\) Range\(f func\(key K, value V\) bool\)](<#HashMap[K, V].Range>) 96 | - [func \(n \*HashMap\[K, V\]\) Set\(key K, value V\)](<#HashMap[K, V].Set>) 97 | - [func \(n \*HashMap\[K, V\]\) Values\(\) \[\]V](<#HashMap[K, V].Values>) 98 | - [type Node](<#Node>) 99 | - [type PriorityQueue](<#PriorityQueue>) 100 | - [func NewPriorityQueue\[T any\]\(\) \*PriorityQueue\[T\]](<#NewPriorityQueue>) 101 | - [func \(q \*PriorityQueue\[T\]\) Len\(\) int](<#PriorityQueue[T].Len>) 102 | - [func \(q \*PriorityQueue\[T\]\) Peek\(\) \(T, bool\)](<#PriorityQueue[T].Peek>) 103 | - [func \(q \*PriorityQueue\[T\]\) Pop\(\) \(T, bool\)](<#PriorityQueue[T].Pop>) 104 | - [func \(q \*PriorityQueue\[T\]\) Push\(item T, weight float64\)](<#PriorityQueue[T].Push>) 105 | - [func \(q \*PriorityQueue\[T\]\) UpdatePriority\(value T, priority float64\)](<#PriorityQueue[T].UpdatePriority>) 106 | - [type Queue](<#Queue>) 107 | - [func NewQueue\[T any\]\(\) \*Queue\[T\]](<#NewQueue>) 108 | - [func \(s \*Queue\[T\]\) Len\(\) int](<#Queue[T].Len>) 109 | - [func \(s \*Queue\[T\]\) Peek\(\) \(T, bool\)](<#Queue[T].Peek>) 110 | - [func \(s \*Queue\[T\]\) Pop\(\) \(T, bool\)](<#Queue[T].Pop>) 111 | - [func \(s \*Queue\[T\]\) Push\(f T\)](<#Queue[T].Push>) 112 | - [func \(q \*Queue\[T\]\) Range\(fn func\(element T\) bool\)](<#Queue[T].Range>) 113 | - [func \(q \*Queue\[T\]\) RangeUntil\(fn func\(element T\) bool, done chan struct\{\}\)](<#Queue[T].RangeUntil>) 114 | - [type Set](<#Set>) 115 | - [func NewSet\[T comparable\]\(\) \*Set\[T\]](<#NewSet>) 116 | - [func \(s \*Set\[T\]\) Add\(val T\)](<#Set[T].Add>) 117 | - [func \(s \*Set\[T\]\) Contains\(val T\) bool](<#Set[T].Contains>) 118 | - [func \(s \*Set\[T\]\) Len\(\) int](<#Set[T].Len>) 119 | - [func \(s \*Set\[T\]\) Range\(fn func\(element T\) bool\)](<#Set[T].Range>) 120 | - [func \(s \*Set\[T\]\) Remove\(val T\)](<#Set[T].Remove>) 121 | - [func \(s \*Set\[T\]\) Sort\(lessFunc func\(i T, j T\) bool\) \[\]T](<#Set[T].Sort>) 122 | - [func \(s \*Set\[T\]\) Values\(\) \[\]T](<#Set[T].Values>) 123 | - [type Stack](<#Stack>) 124 | - [func NewStack\[T any\]\(\) \*Stack\[T\]](<#NewStack>) 125 | - [func \(s \*Stack\[T\]\) Clear\(\)](<#Stack[T].Clear>) 126 | - [func \(s \*Stack\[T\]\) Len\(\) int](<#Stack[T].Len>) 127 | - [func \(s \*Stack\[T\]\) Peek\(\) \(T, bool\)](<#Stack[T].Peek>) 128 | - [func \(s \*Stack\[T\]\) Pop\(\) \(T, bool\)](<#Stack[T].Pop>) 129 | - [func \(s \*Stack\[T\]\) Push\(f T\)](<#Stack[T].Push>) 130 | - [func \(s \*Stack\[T\]\) Range\(fn func\(element T\) bool\)](<#Stack[T].Range>) 131 | - [func \(s \*Stack\[T\]\) RangeUntil\(fn func\(element T\) bool, done chan struct\{\}\)](<#Stack[T].RangeUntil>) 132 | - [func \(s \*Stack\[T\]\) Sort\(lessFunc func\(i T, j T\) bool\) \[\]T](<#Stack[T].Sort>) 133 | - [func \(s \*Stack\[T\]\) Values\(\) \[\]T](<#Stack[T].Values>) 134 | 135 | 136 | 137 | ## func [UniqueID]() 138 | 139 | ```go 140 | func UniqueID(prefix string) string 141 | ``` 142 | 143 | UniqueID returns a unique identifier with the given prefix 144 | 145 | 146 | ## type [BoundedQueue]() 147 | 148 | BoundedQueue is a basic FIFO BoundedQueue based on a buffered channel 149 | 150 | ```go 151 | type BoundedQueue[T any] struct { 152 | // contains filtered or unexported fields 153 | } 154 | ``` 155 | 156 | 157 | ### func [NewBoundedQueue]() 158 | 159 | ```go 160 | func NewBoundedQueue[T any](maxSize int) *BoundedQueue[T] 161 | ``` 162 | 163 | NewBoundedQueue returns a new BoundedQueue with the given max size. When the max size is reached, the queue will block until a value is removed. If maxSize is 0, the queue will always block until a value is removed. The BoundedQueue is concurrent\-safe. 164 | 165 | 166 | ### func \(\*BoundedQueue\[T\]\) [Close]() 167 | 168 | ```go 169 | func (q *BoundedQueue[T]) Close() 170 | ``` 171 | 172 | Close closes the BoundedQueue channel. 173 | 174 | 175 | ### func \(\*BoundedQueue\[T\]\) [Len]() 176 | 177 | ```go 178 | func (q *BoundedQueue[T]) Len() int 179 | ``` 180 | 181 | Len returns the number of elements in the BoundedQueue. 182 | 183 | 184 | ### func \(\*BoundedQueue\[T\]\) [Pop]() 185 | 186 | ```go 187 | func (q *BoundedQueue[T]) Pop() (T, bool) 188 | ``` 189 | 190 | Pop removes and returns an element from the beginning of the BoundedQueue. 191 | 192 | 193 | ### func \(\*BoundedQueue\[T\]\) [PopContext]() 194 | 195 | ```go 196 | func (q *BoundedQueue[T]) PopContext(ctx context.Context) (T, bool) 197 | ``` 198 | 199 | PopContext removes and returns an element from the beginning of the BoundedQueue. If no element is available, it will block until an element is available or the context is cancelled. 200 | 201 | 202 | ### func \(\*BoundedQueue\[T\]\) [Push]() 203 | 204 | ```go 205 | func (q *BoundedQueue[T]) Push(val T) bool 206 | ``` 207 | 208 | Push adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added. If the queue is full, it will block until an element is removed. 209 | 210 | 211 | ### func \(\*BoundedQueue\[T\]\) [PushContext]() 212 | 213 | ```go 214 | func (q *BoundedQueue[T]) PushContext(ctx context.Context, val T) bool 215 | ``` 216 | 217 | PushContext adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added. If the queue is full, it will block until an element is removed or the context is cancelled. 218 | 219 | 220 | ### func \(\*BoundedQueue\[T\]\) [Range]() 221 | 222 | ```go 223 | func (q *BoundedQueue[T]) Range(fn func(element T) bool) 224 | ``` 225 | 226 | Range executes a provided function once for each BoundedQueue element until it returns false. 227 | 228 | 229 | ### func \(\*BoundedQueue\[T\]\) [RangeContext]() 230 | 231 | ```go 232 | func (q *BoundedQueue[T]) RangeContext(ctx context.Context, fn func(element T) bool) 233 | ``` 234 | 235 | RangeContext executes a provided function once for each BoundedQueue element until it returns false or a value is sent to the done channel. Use this function when you want to continuously process items from the queue until a done signal is received. 236 | 237 | 238 | ## type [DAG]() 239 | 240 | DAG is a concurrency safe, mutable, in\-memory directed graph 241 | 242 | ```go 243 | type DAG[T Node] struct { 244 | // contains filtered or unexported fields 245 | } 246 | ``` 247 | 248 | 249 | ### func [NewDAG]() 250 | 251 | ```go 252 | func NewDAG[T Node](opts ...DagOpt) (*DAG[T], error) 253 | ``` 254 | 255 | NewDAG creates a new Directed Acyclic Graph instance 256 | 257 | 258 | ### func \(\*DAG\[T\]\) [Acyclic]() 259 | 260 | ```go 261 | func (g *DAG[T]) Acyclic() bool 262 | ``` 263 | 264 | Acyclic returns true if the graph contains no cycles. 265 | 266 | 267 | ### func \(\*DAG\[T\]\) [BFS]() 268 | 269 | ```go 270 | func (g *DAG[T]) BFS(ctx context.Context, reverse bool, start *GraphNode[T], search GraphSearchFunc[T]) error 271 | ``` 272 | 273 | BFS executes a depth first search on the graph starting from the current node. The reverse parameter determines whether the search is reversed or not. The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped. 274 | 275 | 276 | ### func \(\*DAG\[T\]\) [DFS]() 277 | 278 | ```go 279 | func (g *DAG[T]) DFS(ctx context.Context, reverse bool, start *GraphNode[T], fn GraphSearchFunc[T]) error 280 | ``` 281 | 282 | DFS executes a depth first search on the graph starting from the current node. The reverse parameter determines whether the search is reversed or not. The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped. 283 | 284 | 285 | ### func \(\*DAG\[T\]\) [GetEdge]() 286 | 287 | ```go 288 | func (g *DAG[T]) GetEdge(id string) (*GraphEdge[T], bool) 289 | ``` 290 | 291 | GetEdge returns the edge with the given id 292 | 293 | 294 | ### func \(\*DAG\[T\]\) [GetEdges]() 295 | 296 | ```go 297 | func (g *DAG[T]) GetEdges() []*GraphEdge[T] 298 | ``` 299 | 300 | GetEdges returns all edges in the graph 301 | 302 | 303 | ### func \(\*DAG\[T\]\) [GetNode]() 304 | 305 | ```go 306 | func (g *DAG[T]) GetNode(id string) (*GraphNode[T], bool) 307 | ``` 308 | 309 | GetNode returns the node with the given id 310 | 311 | 312 | ### func \(\*DAG\[T\]\) [GetNodes]() 313 | 314 | ```go 315 | func (g *DAG[T]) GetNodes() []*GraphNode[T] 316 | ``` 317 | 318 | GetNodes returns all nodes in the graph 319 | 320 | 321 | ### func \(\*DAG\[T\]\) [GraphViz]() 322 | 323 | ```go 324 | func (g *DAG[T]) GraphViz() (image.Image, error) 325 | ``` 326 | 327 | GraphViz returns a graphviz image 328 | 329 | 330 | ### func \(\*DAG\[T\]\) [HasEdge]() 331 | 332 | ```go 333 | func (g *DAG[T]) HasEdge(id string) bool 334 | ``` 335 | 336 | HasEdge returns true if the edge with the given id exists in the graph 337 | 338 | 339 | ### func \(\*DAG\[T\]\) [HasNode]() 340 | 341 | ```go 342 | func (g *DAG[T]) HasNode(id string) bool 343 | ``` 344 | 345 | HasNode returns true if the node with the given id exists in the graph 346 | 347 | 348 | ### func \(\*DAG\[T\]\) [RangeEdges]() 349 | 350 | ```go 351 | func (g *DAG[T]) RangeEdges(fn func(e *GraphEdge[T]) bool) 352 | ``` 353 | 354 | RangeEdges iterates over all edges in the graph 355 | 356 | 357 | ### func \(\*DAG\[T\]\) [RangeNodes]() 358 | 359 | ```go 360 | func (g *DAG[T]) RangeNodes(fn func(n *GraphNode[T]) bool) 361 | ``` 362 | 363 | RangeNodes iterates over all nodes in the graph 364 | 365 | 366 | ### func \(\*DAG\[T\]\) [SetNode]() 367 | 368 | ```go 369 | func (g *DAG[T]) SetNode(node Node) *GraphNode[T] 370 | ``` 371 | 372 | SetNode sets a node in the graph \- it will use the node's ID as the key and overwrite any existing node with the same ID 373 | 374 | 375 | ### func \(\*DAG\[T\]\) [Size]() 376 | 377 | ```go 378 | func (g *DAG[T]) Size() (int, int) 379 | ``` 380 | 381 | Size returns the number of nodes and edges in the graph 382 | 383 | 384 | ### func \(\*DAG\[T\]\) [TopologicalSort]() 385 | 386 | ```go 387 | func (g *DAG[T]) TopologicalSort(reverse bool) ([]*GraphNode[T], error) 388 | ``` 389 | 390 | 391 | 392 | 393 | ## type [DagOpt]() 394 | 395 | DagOpt is an option for configuring a DAG 396 | 397 | ```go 398 | type DagOpt func(*dagOpts) 399 | ``` 400 | 401 | 402 | ### func [WithVizualization]() 403 | 404 | ```go 405 | func WithVizualization() DagOpt 406 | ``` 407 | 408 | WithVizualization enables graphviz visualization on the DAG 409 | 410 | 411 | ## type [GraphEdge]() 412 | 413 | GraphEdge is a relationship between two nodes 414 | 415 | ```go 416 | type GraphEdge[T Node] struct { 417 | // contains filtered or unexported fields 418 | } 419 | ``` 420 | 421 | 422 | ### func \(\*GraphEdge\[T\]\) [From]() 423 | 424 | ```go 425 | func (n *GraphEdge[T]) From() *GraphNode[T] 426 | ``` 427 | 428 | From returns the from node of the edge 429 | 430 | 431 | ### func \(\*GraphEdge\[T\]\) [ID]() 432 | 433 | ```go 434 | func (n *GraphEdge[T]) ID() string 435 | ``` 436 | 437 | ID returns the unique identifier of the node 438 | 439 | 440 | ### func \(\*GraphEdge\[T\]\) [Metadata]() 441 | 442 | ```go 443 | func (n *GraphEdge[T]) Metadata() map[string]string 444 | ``` 445 | 446 | Metadata returns the metadata of the node 447 | 448 | 449 | ### func \(\*GraphEdge\[T\]\) [Relationship]() 450 | 451 | ```go 452 | func (n *GraphEdge[T]) Relationship() string 453 | ``` 454 | 455 | Relationship returns the relationship between the two nodes 456 | 457 | 458 | ### func \(\*GraphEdge\[T\]\) [SetMetadata]() 459 | 460 | ```go 461 | func (n *GraphEdge[T]) SetMetadata(metadata map[string]string) 462 | ``` 463 | 464 | SetMetadata sets the metadata of the node 465 | 466 | 467 | ### func \(\*GraphEdge\[T\]\) [To]() 468 | 469 | ```go 470 | func (n *GraphEdge[T]) To() *GraphNode[T] 471 | ``` 472 | 473 | To returns the to node of the edge 474 | 475 | 476 | ## type [GraphNode]() 477 | 478 | GraphNode is a node in the graph. It can be connected to other nodes via edges. 479 | 480 | ```go 481 | type GraphNode[T Node] struct { 482 | Node 483 | // contains filtered or unexported fields 484 | } 485 | ``` 486 | 487 | 488 | ### func \(\*GraphNode\[T\]\) [Ancestors]() 489 | 490 | ```go 491 | func (n *GraphNode[T]) Ancestors(fn func(node *GraphNode[T]) bool) 492 | ``` 493 | 494 | Ancestors returns the ancestors of the current node 495 | 496 | 497 | ### func \(\*GraphNode\[T\]\) [BFS]() 498 | 499 | ```go 500 | func (n *GraphNode[T]) BFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error 501 | ``` 502 | 503 | BFS performs a breadth\-first search on the graph starting from the current node 504 | 505 | 506 | ### func \(\*GraphNode\[T\]\) [DFS]() 507 | 508 | ```go 509 | func (n *GraphNode[T]) DFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error 510 | ``` 511 | 512 | DFS performs a depth\-first search on the graph starting from the current node 513 | 514 | 515 | ### func \(\*GraphNode\[T\]\) [Descendants]() 516 | 517 | ```go 518 | func (n *GraphNode[T]) Descendants(fn func(node *GraphNode[T]) bool) 519 | ``` 520 | 521 | Descendants returns the descendants of the current node 522 | 523 | 524 | ### func \(\*GraphNode\[T\]\) [EdgesFrom]() 525 | 526 | ```go 527 | func (n *GraphNode[T]) EdgesFrom(relationship string, fn func(e *GraphEdge[T]) bool) 528 | ``` 529 | 530 | EdgesFrom iterates over the edges from the current node to other nodes with the given relationship. If the relationship is empty, all relationships will be iterated over. 531 | 532 | 533 | ### func \(\*GraphNode\[T\]\) [EdgesTo]() 534 | 535 | ```go 536 | func (n *GraphNode[T]) EdgesTo(relationship string, fn func(e *GraphEdge[T]) bool) 537 | ``` 538 | 539 | EdgesTo iterates over the edges from other nodes to the current node with the given relationship. If the relationship is empty, all relationships will be iterated over. 540 | 541 | 542 | ### func \(\*GraphNode\[T\]\) [Graph]() 543 | 544 | ```go 545 | func (n *GraphNode[T]) Graph() *DAG[T] 546 | ``` 547 | 548 | DirectedGraph returns the graph the node belongs to 549 | 550 | 551 | ### func \(\*GraphNode\[T\]\) [IsConnectedTo]() 552 | 553 | ```go 554 | func (n *GraphNode[T]) IsConnectedTo(node *GraphNode[T]) bool 555 | ``` 556 | 557 | IsConnectedTo returns true if the current node is connected to the given node in any direction 558 | 559 | 560 | ### func \(\*GraphNode\[T\]\) [Remove]() 561 | 562 | ```go 563 | func (n *GraphNode[T]) Remove() error 564 | ``` 565 | 566 | Remove removes the current node from the graph 567 | 568 | 569 | ### func \(\*GraphNode\[T\]\) [RemoveEdge]() 570 | 571 | ```go 572 | func (n *GraphNode[T]) RemoveEdge(edgeID string) 573 | ``` 574 | 575 | RemoveEdge removes an edge from the current node by edgeID 576 | 577 | 578 | ### func \(\*GraphNode\[T\]\) [SetEdge]() 579 | 580 | ```go 581 | func (n *GraphNode[T]) SetEdge(relationship string, toNode Node, metadata map[string]string) (*GraphEdge[T], error) 582 | ``` 583 | 584 | SetEdge sets an edge from the current node to the node with the given nodeID. If the nodeID does not exist, an error is returned. If the edgeID is empty, a unique id will be generated. If the metadata is nil, an empty map will be used. 585 | 586 | 587 | ## type [GraphSearchFunc]() 588 | 589 | GraphSearchFunc is a function that is called on each node in the graph during a search 590 | 591 | ```go 592 | type GraphSearchFunc[T Node] func(ctx context.Context, relationship string, node *GraphNode[T]) bool 593 | ``` 594 | 595 | 596 | ## type [HashMap]() 597 | 598 | HashMap is a thread safe map 599 | 600 | ```go 601 | type HashMap[K comparable, V any] struct { 602 | // contains filtered or unexported fields 603 | } 604 | ``` 605 | 606 | 607 | ### func [NewHashMap]() 608 | 609 | ```go 610 | func NewHashMap[K comparable, V any]() *HashMap[K, V] 611 | ``` 612 | 613 | NewHashMap creates a new generic hash map 614 | 615 | 616 | ### func \(\*HashMap\[K, V\]\) [Clear]() 617 | 618 | ```go 619 | func (n *HashMap[K, V]) Clear() 620 | ``` 621 | 622 | Clear clears the map 623 | 624 | 625 | ### func \(\*HashMap\[K, V\]\) [Delete]() 626 | 627 | ```go 628 | func (n *HashMap[K, V]) Delete(key K) 629 | ``` 630 | 631 | Delete deletes the key from the map 632 | 633 | 634 | ### func \(\*HashMap\[K, V\]\) [Exists]() 635 | 636 | ```go 637 | func (n *HashMap[K, V]) Exists(key K) bool 638 | ``` 639 | 640 | Exists returns true if the key exists in the map 641 | 642 | 643 | ### func \(\*HashMap\[K, V\]\) [Filter]() 644 | 645 | ```go 646 | func (n *HashMap[K, V]) Filter(f func(key K, value V) bool) *HashMap[K, V] 647 | ``` 648 | 649 | Filter returns a new hashmap with the values that return true from the function 650 | 651 | 652 | ### func \(\*HashMap\[K, V\]\) [Get]() 653 | 654 | ```go 655 | func (n *HashMap[K, V]) Get(key K) (V, bool) 656 | ``` 657 | 658 | Get gets the value from the key 659 | 660 | 661 | ### func \(\*HashMap\[K, V\]\) [Keys]() 662 | 663 | ```go 664 | func (n *HashMap[K, V]) Keys() []K 665 | ``` 666 | 667 | Keys returns a copy of the keys in the map as a slice 668 | 669 | 670 | ### func \(\*HashMap\[K, V\]\) [Len]() 671 | 672 | ```go 673 | func (n *HashMap[K, V]) Len() int 674 | ``` 675 | 676 | Len returns the length of the map 677 | 678 | 679 | ### func \(\*HashMap\[K, V\]\) [Map]() 680 | 681 | ```go 682 | func (n *HashMap[K, V]) Map() map[K]V 683 | ``` 684 | 685 | Map returns a copy of the hashmap as a map\[string\]T 686 | 687 | 688 | ### func \(\*HashMap\[K, V\]\) [Range]() 689 | 690 | ```go 691 | func (n *HashMap[K, V]) Range(f func(key K, value V) bool) 692 | ``` 693 | 694 | Range ranges over the map with a function until false is returned 695 | 696 | 697 | ### func \(\*HashMap\[K, V\]\) [Set]() 698 | 699 | ```go 700 | func (n *HashMap[K, V]) Set(key K, value V) 701 | ``` 702 | 703 | Set sets the key to the value 704 | 705 | 706 | ### func \(\*HashMap\[K, V\]\) [Values]() 707 | 708 | ```go 709 | func (n *HashMap[K, V]) Values() []V 710 | ``` 711 | 712 | Values returns a copy of the values in the map as a slice 713 | 714 | 715 | ## type [Node]() 716 | 717 | Node is a node in the graph. It can be connected to other nodes via edges. 718 | 719 | ```go 720 | type Node interface { 721 | // ID returns the unique identifier of the node 722 | ID() string 723 | // Metadata returns the metadata of the node 724 | Metadata() map[string]string 725 | // SetMetadata sets the metadata of the node 726 | SetMetadata(metadata map[string]string) 727 | } 728 | ``` 729 | 730 | 731 | ## type [PriorityQueue]() 732 | 733 | PriorityQueue is a thread safe priority queue 734 | 735 | ```go 736 | type PriorityQueue[T any] struct { 737 | // contains filtered or unexported fields 738 | } 739 | ``` 740 | 741 | 742 | ### func [NewPriorityQueue]() 743 | 744 | ```go 745 | func NewPriorityQueue[T any]() *PriorityQueue[T] 746 | ``` 747 | 748 | NewPriorityQueue creates a new priority queue 749 | 750 | 751 | ### func \(\*PriorityQueue\[T\]\) [Len]() 752 | 753 | ```go 754 | func (q *PriorityQueue[T]) Len() int 755 | ``` 756 | 757 | Len returns the length of the queue 758 | 759 | 760 | ### func \(\*PriorityQueue\[T\]\) [Peek]() 761 | 762 | ```go 763 | func (q *PriorityQueue[T]) Peek() (T, bool) 764 | ``` 765 | 766 | Peek returns the next item in the queue without removing it 767 | 768 | 769 | ### func \(\*PriorityQueue\[T\]\) [Pop]() 770 | 771 | ```go 772 | func (q *PriorityQueue[T]) Pop() (T, bool) 773 | ``` 774 | 775 | Pop pops an item off the queue 776 | 777 | 778 | ### func \(\*PriorityQueue\[T\]\) [Push]() 779 | 780 | ```go 781 | func (q *PriorityQueue[T]) Push(item T, weight float64) 782 | ``` 783 | 784 | Push pushes an item onto the queue 785 | 786 | 787 | ### func \(\*PriorityQueue\[T\]\) [UpdatePriority]() 788 | 789 | ```go 790 | func (q *PriorityQueue[T]) UpdatePriority(value T, priority float64) 791 | ``` 792 | 793 | 794 | 795 | 796 | ## type [Queue]() 797 | 798 | Queue is a thread safe non\-blocking queue 799 | 800 | ```go 801 | type Queue[T any] struct { 802 | // contains filtered or unexported fields 803 | } 804 | ``` 805 | 806 | 807 | ### func [NewQueue]() 808 | 809 | ```go 810 | func NewQueue[T any]() *Queue[T] 811 | ``` 812 | 813 | NewQueue returns a new Queue 814 | 815 | 816 | ### func \(\*Queue\[T\]\) [Len]() 817 | 818 | ```go 819 | func (s *Queue[T]) Len() int 820 | ``` 821 | 822 | Len returns the length of the queue 823 | 824 | 825 | ### func \(\*Queue\[T\]\) [Peek]() 826 | 827 | ```go 828 | func (s *Queue[T]) Peek() (T, bool) 829 | ``` 830 | 831 | Peek returns the next item in the queue without removing it 832 | 833 | 834 | ### func \(\*Queue\[T\]\) [Pop]() 835 | 836 | ```go 837 | func (s *Queue[T]) Pop() (T, bool) 838 | ``` 839 | 840 | Pop and return top element of Queue. Return false if Queue is empty. 841 | 842 | 843 | ### func \(\*Queue\[T\]\) [Push]() 844 | 845 | ```go 846 | func (s *Queue[T]) Push(f T) 847 | ``` 848 | 849 | Push a new value onto the Queue 850 | 851 | 852 | ### func \(\*Queue\[T\]\) [Range]() 853 | 854 | ```go 855 | func (q *Queue[T]) Range(fn func(element T) bool) 856 | ``` 857 | 858 | Range executes a provided function once for each Queue element until it returns false or the Queue is empty. 859 | 860 | 861 | ### func \(\*Queue\[T\]\) [RangeUntil]() 862 | 863 | ```go 864 | func (q *Queue[T]) RangeUntil(fn func(element T) bool, done chan struct{}) 865 | ``` 866 | 867 | RangeUntil executes a provided function once for each Queue element until it returns false or a value is sent on the done channel. Use this function when you want to continuously process items from the queue until a done signal is received. 868 | 869 | 870 | ## type [Set]() 871 | 872 | Set is a basic thread\-safe Set implementation. 873 | 874 | ```go 875 | type Set[T comparable] struct { 876 | // contains filtered or unexported fields 877 | } 878 | ``` 879 | 880 | 881 | ### func [NewSet]() 882 | 883 | ```go 884 | func NewSet[T comparable]() *Set[T] 885 | ``` 886 | 887 | NewSet returns a new Set with the given initial size. 888 | 889 | 890 | ### func \(\*Set\[T\]\) [Add]() 891 | 892 | ```go 893 | func (s *Set[T]) Add(val T) 894 | ``` 895 | 896 | Add adds an element to the Set. 897 | 898 | 899 | ### func \(\*Set\[T\]\) [Contains]() 900 | 901 | ```go 902 | func (s *Set[T]) Contains(val T) bool 903 | ``` 904 | 905 | Contains returns true if the Set contains the element. 906 | 907 | 908 | ### func \(\*Set\[T\]\) [Len]() 909 | 910 | ```go 911 | func (s *Set[T]) Len() int 912 | ``` 913 | 914 | Len returns the number of elements in the Set. 915 | 916 | 917 | ### func \(\*Set\[T\]\) [Range]() 918 | 919 | ```go 920 | func (s *Set[T]) Range(fn func(element T) bool) 921 | ``` 922 | 923 | Range executes a provided function once for each Set element until it returns false. 924 | 925 | 926 | ### func \(\*Set\[T\]\) [Remove]() 927 | 928 | ```go 929 | func (s *Set[T]) Remove(val T) 930 | ``` 931 | 932 | Remove removes an element from the Set. 933 | 934 | 935 | ### func \(\*Set\[T\]\) [Sort]() 936 | 937 | ```go 938 | func (s *Set[T]) Sort(lessFunc func(i T, j T) bool) []T 939 | ``` 940 | 941 | Sort returns the values of the set as an array sorted by the provided less function 942 | 943 | 944 | ### func \(\*Set\[T\]\) [Values]() 945 | 946 | ```go 947 | func (s *Set[T]) Values() []T 948 | ``` 949 | 950 | Values returns the values of the set as an array 951 | 952 | 953 | ## type [Stack]() 954 | 955 | Stack is a basic LIFO Stack 956 | 957 | ```go 958 | type Stack[T any] struct { 959 | // contains filtered or unexported fields 960 | } 961 | ``` 962 | 963 | 964 | ### func [NewStack]() 965 | 966 | ```go 967 | func NewStack[T any]() *Stack[T] 968 | ``` 969 | 970 | NewStack returns a new Stack instance 971 | 972 | 973 | ### func \(\*Stack\[T\]\) [Clear]() 974 | 975 | ```go 976 | func (s *Stack[T]) Clear() 977 | ``` 978 | 979 | Clear removes all elements from the Stack 980 | 981 | 982 | ### func \(\*Stack\[T\]\) [Len]() 983 | 984 | ```go 985 | func (s *Stack[T]) Len() int 986 | ``` 987 | 988 | Len returns the number of elements in the Stack. 989 | 990 | 991 | ### func \(\*Stack\[T\]\) [Peek]() 992 | 993 | ```go 994 | func (s *Stack[T]) Peek() (T, bool) 995 | ``` 996 | 997 | Peek returns the top element of the Stack without removing it. Return false if Stack is empty. 998 | 999 | 1000 | ### func \(\*Stack\[T\]\) [Pop]() 1001 | 1002 | ```go 1003 | func (s *Stack[T]) Pop() (T, bool) 1004 | ``` 1005 | 1006 | Pop removes and return top element of Stack. Return false if Stack is empty. 1007 | 1008 | 1009 | ### func \(\*Stack\[T\]\) [Push]() 1010 | 1011 | ```go 1012 | func (s *Stack[T]) Push(f T) 1013 | ``` 1014 | 1015 | Push a new value onto the Stack \(LIFO\) 1016 | 1017 | 1018 | ### func \(\*Stack\[T\]\) [Range]() 1019 | 1020 | ```go 1021 | func (s *Stack[T]) Range(fn func(element T) bool) 1022 | ``` 1023 | 1024 | Range executes a provided function once for each Stack element until it returns false. 1025 | 1026 | 1027 | ### func \(\*Stack\[T\]\) [RangeUntil]() 1028 | 1029 | ```go 1030 | func (s *Stack[T]) RangeUntil(fn func(element T) bool, done chan struct{}) 1031 | ``` 1032 | 1033 | RangeUntil executes a provided function once after calling Pop on the stack until the function returns false or a value is sent on the done channel. Use this function when you want to continuously process items from the stack until a done signal is received. 1034 | 1035 | 1036 | ### func \(\*Stack\[T\]\) [Sort]() 1037 | 1038 | ```go 1039 | func (s *Stack[T]) Sort(lessFunc func(i T, j T) bool) []T 1040 | ``` 1041 | 1042 | Sort returns the values of the stack as an array sorted by the provided less function 1043 | 1044 | 1045 | ### func \(\*Stack\[T\]\) [Values]() 1046 | 1047 | ```go 1048 | func (s *Stack[T]) Values() []T 1049 | ``` 1050 | 1051 | Values returns the values of the stack as an array 1052 | 1053 | Generated by [gomarkdoc]() 1054 | -------------------------------------------------------------------------------- /cover.out: -------------------------------------------------------------------------------- 1 | mode: atomic 2 | github.com/autom8ter/dagger/v3/dagger.go:50.37,53.16 3 100 3 | github.com/autom8ter/dagger/v3/dagger.go:53.16,54.13 1 0 4 | github.com/autom8ter/dagger/v3/dagger.go:56.2,56.18 1 100 5 | github.com/autom8ter/dagger/v3/dagger.go:56.18,58.3 1 0 6 | github.com/autom8ter/dagger/v3/dagger.go:59.2,59.60 1 100 7 | github.com/autom8ter/dagger/v3/dagger.go:78.36,80.2 1 1532 8 | github.com/autom8ter/dagger/v3/dagger.go:83.53,85.2 1 0 9 | github.com/autom8ter/dagger/v3/dagger.go:88.45,90.2 1 200 10 | github.com/autom8ter/dagger/v3/dagger.go:93.43,95.2 1 1200 11 | github.com/autom8ter/dagger/v3/dagger.go:98.46,100.2 1 200 12 | github.com/autom8ter/dagger/v3/dagger.go:103.64,104.29 1 0 13 | github.com/autom8ter/dagger/v3/dagger.go:104.29,106.3 1 0 14 | github.com/autom8ter/dagger/v3/dagger.go:129.92,131.2 1 0 15 | github.com/autom8ter/dagger/v3/dagger.go:134.92,136.2 1 0 16 | github.com/autom8ter/dagger/v3/dagger.go:140.86,143.62 3 2 17 | github.com/autom8ter/dagger/v3/dagger.go:143.62,144.64 1 1 18 | github.com/autom8ter/dagger/v3/dagger.go:144.64,146.4 1 0 19 | github.com/autom8ter/dagger/v3/dagger.go:147.3,147.18 1 1 20 | github.com/autom8ter/dagger/v3/dagger.go:153.84,156.60 3 2 21 | github.com/autom8ter/dagger/v3/dagger.go:156.60,157.64 1 1 22 | github.com/autom8ter/dagger/v3/dagger.go:157.64,159.4 1 0 23 | github.com/autom8ter/dagger/v3/dagger.go:160.3,160.18 1 1 24 | github.com/autom8ter/dagger/v3/dagger.go:168.117,169.21 1 507 25 | github.com/autom8ter/dagger/v3/dagger.go:169.21,171.3 1 0 26 | github.com/autom8ter/dagger/v3/dagger.go:172.2,175.9 4 507 27 | github.com/autom8ter/dagger/v3/dagger.go:175.9,177.3 1 0 28 | github.com/autom8ter/dagger/v3/dagger.go:178.2,187.31 5 507 29 | github.com/autom8ter/dagger/v3/dagger.go:187.31,189.17 2 1 30 | github.com/autom8ter/dagger/v3/dagger.go:189.17,191.4 1 0 31 | github.com/autom8ter/dagger/v3/dagger.go:192.3,193.41 2 1 32 | github.com/autom8ter/dagger/v3/dagger.go:193.41,195.4 1 0 33 | github.com/autom8ter/dagger/v3/dagger.go:196.3,196.41 1 1 34 | github.com/autom8ter/dagger/v3/dagger.go:196.41,198.4 1 0 35 | github.com/autom8ter/dagger/v3/dagger.go:199.3,199.49 1 1 36 | github.com/autom8ter/dagger/v3/dagger.go:199.49,201.4 1 0 37 | github.com/autom8ter/dagger/v3/dagger.go:202.3,202.43 1 1 38 | github.com/autom8ter/dagger/v3/dagger.go:202.43,205.4 2 0 39 | github.com/autom8ter/dagger/v3/dagger.go:206.3,206.47 1 1 40 | github.com/autom8ter/dagger/v3/dagger.go:206.47,209.4 2 0 41 | github.com/autom8ter/dagger/v3/dagger.go:210.3,210.14 1 1 42 | github.com/autom8ter/dagger/v3/dagger.go:212.2,212.15 1 507 43 | github.com/autom8ter/dagger/v3/dagger.go:216.50,220.2 3 1 44 | github.com/autom8ter/dagger/v3/dagger.go:222.50,224.9 2 2 45 | github.com/autom8ter/dagger/v3/dagger.go:224.9,226.3 1 0 46 | github.com/autom8ter/dagger/v3/dagger.go:227.2,230.22 4 2 47 | github.com/autom8ter/dagger/v3/dagger.go:230.22,232.3 1 0 48 | github.com/autom8ter/dagger/v3/dagger.go:236.39,237.60 1 1 49 | github.com/autom8ter/dagger/v3/dagger.go:237.60,240.3 2 0 50 | github.com/autom8ter/dagger/v3/dagger.go:241.2,241.62 1 1 51 | github.com/autom8ter/dagger/v3/dagger.go:241.62,244.3 2 1 52 | github.com/autom8ter/dagger/v3/dagger.go:245.2,246.31 2 1 53 | github.com/autom8ter/dagger/v3/dagger.go:246.31,248.3 1 0 54 | github.com/autom8ter/dagger/v3/dagger.go:249.2,249.12 1 1 55 | github.com/autom8ter/dagger/v3/dagger.go:253.40,255.2 1 0 56 | github.com/autom8ter/dagger/v3/dagger.go:258.68,263.2 4 0 57 | github.com/autom8ter/dagger/v3/dagger.go:265.90,266.60 1 0 58 | github.com/autom8ter/dagger/v3/dagger.go:266.60,267.41 1 0 59 | github.com/autom8ter/dagger/v3/dagger.go:267.41,269.4 1 0 60 | github.com/autom8ter/dagger/v3/dagger.go:270.3,271.23 2 0 61 | github.com/autom8ter/dagger/v3/dagger.go:271.23,273.4 1 0 62 | github.com/autom8ter/dagger/v3/dagger.go:274.3,275.14 2 0 63 | github.com/autom8ter/dagger/v3/dagger.go:280.70,285.2 4 0 64 | github.com/autom8ter/dagger/v3/dagger.go:287.92,288.62 1 0 65 | github.com/autom8ter/dagger/v3/dagger.go:288.62,289.39 1 0 66 | github.com/autom8ter/dagger/v3/dagger.go:289.39,291.4 1 0 67 | github.com/autom8ter/dagger/v3/dagger.go:292.3,293.21 2 0 68 | github.com/autom8ter/dagger/v3/dagger.go:293.21,295.4 1 0 69 | github.com/autom8ter/dagger/v3/dagger.go:296.3,297.14 2 0 70 | github.com/autom8ter/dagger/v3/dagger.go:302.63,304.62 2 0 71 | github.com/autom8ter/dagger/v3/dagger.go:304.62,305.24 1 0 72 | github.com/autom8ter/dagger/v3/dagger.go:305.24,308.4 2 0 73 | github.com/autom8ter/dagger/v3/dagger.go:309.3,309.14 1 0 74 | github.com/autom8ter/dagger/v3/dagger.go:311.2,311.60 1 0 75 | github.com/autom8ter/dagger/v3/dagger.go:311.60,312.26 1 0 76 | github.com/autom8ter/dagger/v3/dagger.go:312.26,315.4 2 0 77 | github.com/autom8ter/dagger/v3/dagger.go:316.3,316.14 1 0 78 | github.com/autom8ter/dagger/v3/dagger.go:318.2,318.15 1 0 79 | github.com/autom8ter/dagger/v3/dagger.go:339.33,340.29 1 1 80 | github.com/autom8ter/dagger/v3/dagger.go:340.29,342.3 1 1 81 | github.com/autom8ter/dagger/v3/dagger.go:346.54,349.27 3 13 82 | github.com/autom8ter/dagger/v3/dagger.go:349.27,351.3 1 1 83 | github.com/autom8ter/dagger/v3/dagger.go:352.2,358.23 2 13 84 | github.com/autom8ter/dagger/v3/dagger.go:358.23,361.3 2 1 85 | github.com/autom8ter/dagger/v3/dagger.go:362.2,362.15 1 13 86 | github.com/autom8ter/dagger/v3/dagger.go:366.51,374.25 3 522 87 | github.com/autom8ter/dagger/v3/dagger.go:374.25,376.17 2 2 88 | github.com/autom8ter/dagger/v3/dagger.go:376.17,377.14 1 0 89 | github.com/autom8ter/dagger/v3/dagger.go:379.3,380.48 2 2 90 | github.com/autom8ter/dagger/v3/dagger.go:380.48,382.4 1 0 91 | github.com/autom8ter/dagger/v3/dagger.go:383.3,383.48 1 2 92 | github.com/autom8ter/dagger/v3/dagger.go:383.48,385.4 1 0 93 | github.com/autom8ter/dagger/v3/dagger.go:387.3,387.14 1 2 94 | github.com/autom8ter/dagger/v3/dagger.go:390.2,390.10 1 522 95 | github.com/autom8ter/dagger/v3/dagger.go:394.42,397.2 2 0 96 | github.com/autom8ter/dagger/v3/dagger.go:400.42,403.2 2 0 97 | github.com/autom8ter/dagger/v3/dagger.go:406.59,409.2 2 1 98 | github.com/autom8ter/dagger/v3/dagger.go:412.36,414.2 1 5 99 | github.com/autom8ter/dagger/v3/dagger.go:417.45,419.57 2 0 100 | github.com/autom8ter/dagger/v3/dagger.go:419.57,422.3 2 0 101 | github.com/autom8ter/dagger/v3/dagger.go:423.2,423.14 1 0 102 | github.com/autom8ter/dagger/v3/dagger.go:427.45,429.57 2 0 103 | github.com/autom8ter/dagger/v3/dagger.go:429.57,432.3 2 0 104 | github.com/autom8ter/dagger/v3/dagger.go:433.2,433.14 1 0 105 | github.com/autom8ter/dagger/v3/dagger.go:437.59,440.2 2 3 106 | github.com/autom8ter/dagger/v3/dagger.go:443.60,444.57 1 0 107 | github.com/autom8ter/dagger/v3/dagger.go:444.57,446.3 1 0 108 | github.com/autom8ter/dagger/v3/dagger.go:450.60,451.57 1 0 109 | github.com/autom8ter/dagger/v3/dagger.go:451.57,453.3 1 0 110 | github.com/autom8ter/dagger/v3/dagger.go:462.111,474.17 5 1 111 | github.com/autom8ter/dagger/v3/dagger.go:474.17,476.3 1 0 112 | github.com/autom8ter/dagger/v3/dagger.go:477.2,477.48 1 1 113 | github.com/autom8ter/dagger/v3/dagger.go:477.48,478.38 1 101 114 | github.com/autom8ter/dagger/v3/dagger.go:478.38,480.4 1 100 115 | github.com/autom8ter/dagger/v3/dagger.go:481.3,481.14 1 1 116 | github.com/autom8ter/dagger/v3/dagger.go:483.2,483.12 1 1 117 | github.com/autom8ter/dagger/v3/dagger.go:489.107,494.23 5 1 118 | github.com/autom8ter/dagger/v3/dagger.go:494.23,504.18 2 1 119 | github.com/autom8ter/dagger/v3/dagger.go:504.18,506.4 1 0 120 | github.com/autom8ter/dagger/v3/dagger.go:507.3,507.37 1 1 121 | github.com/autom8ter/dagger/v3/dagger.go:507.37,509.4 1 0 122 | github.com/autom8ter/dagger/v3/dagger.go:510.3,511.13 2 1 123 | github.com/autom8ter/dagger/v3/dagger.go:513.2,513.23 1 1 124 | github.com/autom8ter/dagger/v3/dagger.go:513.23,514.54 1 1 125 | github.com/autom8ter/dagger/v3/dagger.go:514.54,515.39 1 101 126 | github.com/autom8ter/dagger/v3/dagger.go:515.39,517.5 1 100 127 | github.com/autom8ter/dagger/v3/dagger.go:518.4,518.15 1 1 128 | github.com/autom8ter/dagger/v3/dagger.go:520.3,520.13 1 1 129 | github.com/autom8ter/dagger/v3/dagger.go:522.2,522.36 1 1 130 | github.com/autom8ter/dagger/v3/dagger.go:522.36,524.3 1 0 131 | github.com/autom8ter/dagger/v3/dagger.go:525.2,525.12 1 1 132 | github.com/autom8ter/dagger/v3/dagger.go:543.99,544.22 1 101 133 | github.com/autom8ter/dagger/v3/dagger.go:544.22,546.3 1 0 134 | github.com/autom8ter/dagger/v3/dagger.go:547.2,547.23 1 101 135 | github.com/autom8ter/dagger/v3/dagger.go:547.23,549.3 1 0 136 | github.com/autom8ter/dagger/v3/dagger.go:550.2,550.46 1 101 137 | github.com/autom8ter/dagger/v3/dagger.go:550.46,556.20 3 101 138 | github.com/autom8ter/dagger/v3/dagger.go:556.20,557.71 1 0 139 | github.com/autom8ter/dagger/v3/dagger.go:557.71,567.5 2 0 140 | github.com/autom8ter/dagger/v3/dagger.go:568.9,569.73 1 101 141 | github.com/autom8ter/dagger/v3/dagger.go:569.73,579.5 2 100 142 | github.com/autom8ter/dagger/v3/dagger.go:582.2,582.18 1 101 143 | github.com/autom8ter/dagger/v3/dagger.go:595.95,596.22 1 101 144 | github.com/autom8ter/dagger/v3/dagger.go:596.22,598.3 1 0 145 | github.com/autom8ter/dagger/v3/dagger.go:599.2,599.23 1 101 146 | github.com/autom8ter/dagger/v3/dagger.go:599.23,601.3 1 0 147 | github.com/autom8ter/dagger/v3/dagger.go:602.2,602.46 1 101 148 | github.com/autom8ter/dagger/v3/dagger.go:602.46,608.20 3 101 149 | github.com/autom8ter/dagger/v3/dagger.go:608.20,609.30 1 0 150 | github.com/autom8ter/dagger/v3/dagger.go:609.30,610.72 1 0 151 | github.com/autom8ter/dagger/v3/dagger.go:610.72,621.6 2 0 152 | github.com/autom8ter/dagger/v3/dagger.go:622.5,622.15 1 0 153 | github.com/autom8ter/dagger/v3/dagger.go:624.9,625.30 1 101 154 | github.com/autom8ter/dagger/v3/dagger.go:625.30,626.74 1 101 155 | github.com/autom8ter/dagger/v3/dagger.go:626.74,637.6 2 100 156 | github.com/autom8ter/dagger/v3/dagger.go:638.5,638.15 1 101 157 | github.com/autom8ter/dagger/v3/dagger.go:642.2,642.18 1 101 158 | github.com/autom8ter/dagger/v3/dagger.go:646.33,648.58 2 5 159 | github.com/autom8ter/dagger/v3/dagger.go:648.58,649.31 1 505 160 | github.com/autom8ter/dagger/v3/dagger.go:649.31,652.42 3 401 161 | github.com/autom8ter/dagger/v3/dagger.go:652.42,655.5 2 0 162 | github.com/autom8ter/dagger/v3/dagger.go:657.3,657.14 1 505 163 | github.com/autom8ter/dagger/v3/dagger.go:659.2,659.18 1 5 164 | github.com/autom8ter/dagger/v3/dagger.go:663.96,667.65 4 401 165 | github.com/autom8ter/dagger/v3/dagger.go:667.65,668.39 1 500 166 | github.com/autom8ter/dagger/v3/dagger.go:668.39,669.47 1 0 167 | github.com/autom8ter/dagger/v3/dagger.go:669.47,672.5 2 0 168 | github.com/autom8ter/dagger/v3/dagger.go:673.9,673.46 1 500 169 | github.com/autom8ter/dagger/v3/dagger.go:673.46,676.4 2 0 170 | github.com/autom8ter/dagger/v3/dagger.go:677.3,677.14 1 500 171 | github.com/autom8ter/dagger/v3/dagger.go:679.2,679.15 1 401 172 | github.com/autom8ter/dagger/v3/dagger.go:682.73,683.18 1 2 173 | github.com/autom8ter/dagger/v3/dagger.go:683.18,685.3 1 0 174 | github.com/autom8ter/dagger/v3/dagger.go:686.2,689.58 4 2 175 | github.com/autom8ter/dagger/v3/dagger.go:689.58,692.3 2 202 176 | github.com/autom8ter/dagger/v3/dagger.go:693.2,694.22 2 2 177 | github.com/autom8ter/dagger/v3/dagger.go:694.22,697.3 2 202 178 | github.com/autom8ter/dagger/v3/dagger.go:698.2,698.13 1 2 179 | github.com/autom8ter/dagger/v3/dagger.go:698.13,699.56 1 1 180 | github.com/autom8ter/dagger/v3/dagger.go:699.56,701.4 1 50 181 | github.com/autom8ter/dagger/v3/dagger.go:703.2,703.20 1 2 182 | github.com/autom8ter/dagger/v3/dagger.go:706.125,707.35 1 402 183 | github.com/autom8ter/dagger/v3/dagger.go:707.35,709.3 1 200 184 | github.com/autom8ter/dagger/v3/dagger.go:710.2,710.35 1 202 185 | github.com/autom8ter/dagger/v3/dagger.go:710.35,711.21 1 0 186 | github.com/autom8ter/dagger/v3/dagger.go:713.2,714.13 2 202 187 | github.com/autom8ter/dagger/v3/dagger.go:714.13,715.64 1 202 188 | github.com/autom8ter/dagger/v3/dagger.go:715.64,718.4 2 200 189 | github.com/autom8ter/dagger/v3/dagger.go:719.8,720.66 1 0 190 | github.com/autom8ter/dagger/v3/dagger.go:720.66,723.4 2 0 191 | github.com/autom8ter/dagger/v3/dagger.go:725.2,727.18 3 202 192 | github.com/autom8ter/dagger/v3/dagger.go:731.50,732.18 1 1 193 | github.com/autom8ter/dagger/v3/dagger.go:732.18,734.3 1 0 194 | github.com/autom8ter/dagger/v3/dagger.go:735.2,738.16 4 1 195 | github.com/autom8ter/dagger/v3/dagger.go:738.16,740.3 1 0 196 | github.com/autom8ter/dagger/v3/dagger.go:741.2,741.17 1 1 197 | github.com/autom8ter/dagger/v3/dagger.go:745.55,749.2 1 1071 198 | github.com/autom8ter/dagger/v3/dagger.go:757.35,759.49 2 717 199 | github.com/autom8ter/dagger/v3/dagger.go:759.49,762.3 2 21805 200 | github.com/autom8ter/dagger/v3/dagger.go:763.2,763.14 1 717 201 | github.com/autom8ter/dagger/v3/dagger.go:767.46,769.9 2 513 202 | github.com/autom8ter/dagger/v3/dagger.go:769.9,771.3 1 3 203 | github.com/autom8ter/dagger/v3/dagger.go:772.2,772.18 1 510 204 | github.com/autom8ter/dagger/v3/dagger.go:776.45,778.2 1 2143 205 | github.com/autom8ter/dagger/v3/dagger.go:781.39,783.2 1 107 206 | github.com/autom8ter/dagger/v3/dagger.go:786.44,789.2 2 0 207 | github.com/autom8ter/dagger/v3/dagger.go:792.33,793.49 1 0 208 | github.com/autom8ter/dagger/v3/dagger.go:793.49,796.3 2 0 209 | github.com/autom8ter/dagger/v3/dagger.go:800.36,802.49 2 0 210 | github.com/autom8ter/dagger/v3/dagger.go:802.49,805.3 2 0 211 | github.com/autom8ter/dagger/v3/dagger.go:806.2,806.13 1 0 212 | github.com/autom8ter/dagger/v3/dagger.go:810.38,812.49 2 0 213 | github.com/autom8ter/dagger/v3/dagger.go:812.49,815.3 2 0 214 | github.com/autom8ter/dagger/v3/dagger.go:816.2,816.15 1 0 215 | github.com/autom8ter/dagger/v3/dagger.go:820.60,821.49 1 918 216 | github.com/autom8ter/dagger/v3/dagger.go:821.49,823.3 1 11610 217 | github.com/autom8ter/dagger/v3/dagger.go:827.76,829.49 2 0 218 | github.com/autom8ter/dagger/v3/dagger.go:829.49,830.28 1 0 219 | github.com/autom8ter/dagger/v3/dagger.go:830.28,832.4 1 0 220 | github.com/autom8ter/dagger/v3/dagger.go:833.3,833.14 1 0 221 | github.com/autom8ter/dagger/v3/dagger.go:835.2,835.17 1 0 222 | github.com/autom8ter/dagger/v3/dagger.go:839.39,841.49 2 0 223 | github.com/autom8ter/dagger/v3/dagger.go:841.49,844.3 2 0 224 | github.com/autom8ter/dagger/v3/dagger.go:845.2,845.15 1 0 225 | github.com/autom8ter/dagger/v3/dagger.go:861.50,866.2 1 1 226 | github.com/autom8ter/dagger/v3/dagger.go:868.70,871.31 3 0 227 | github.com/autom8ter/dagger/v3/dagger.go:871.31,872.43 1 0 228 | github.com/autom8ter/dagger/v3/dagger.go:872.43,874.44 2 0 229 | github.com/autom8ter/dagger/v3/dagger.go:874.44,876.5 1 0 230 | github.com/autom8ter/dagger/v3/dagger.go:877.4,877.10 1 0 231 | github.com/autom8ter/dagger/v3/dagger.go:883.38,887.2 3 2 232 | github.com/autom8ter/dagger/v3/dagger.go:890.57,894.42 4 100 233 | github.com/autom8ter/dagger/v3/dagger.go:894.42,896.3 1 5673 234 | github.com/autom8ter/dagger/v3/dagger.go:900.44,903.23 3 100 235 | github.com/autom8ter/dagger/v3/dagger.go:903.23,905.3 1 0 236 | github.com/autom8ter/dagger/v3/dagger.go:906.2,908.25 3 100 237 | github.com/autom8ter/dagger/v3/dagger.go:912.45,915.23 3 1 238 | github.com/autom8ter/dagger/v3/dagger.go:915.23,917.3 1 0 239 | github.com/autom8ter/dagger/v3/dagger.go:918.2,918.31 1 1 240 | github.com/autom8ter/dagger/v3/dagger.go:929.59,932.2 2 2 241 | github.com/autom8ter/dagger/v3/dagger.go:935.58,936.6 1 0 242 | github.com/autom8ter/dagger/v3/dagger.go:936.6,937.10 1 0 243 | github.com/autom8ter/dagger/v3/dagger.go:938.24,939.11 1 0 244 | github.com/autom8ter/dagger/v3/dagger.go:939.11,941.5 1 0 245 | github.com/autom8ter/dagger/v3/dagger.go:942.4,942.14 1 0 246 | github.com/autom8ter/dagger/v3/dagger.go:942.14,944.5 1 0 247 | github.com/autom8ter/dagger/v3/dagger.go:945.11,946.22 1 0 248 | github.com/autom8ter/dagger/v3/dagger.go:946.22,948.5 1 0 249 | github.com/autom8ter/dagger/v3/dagger.go:955.83,956.6 1 1 250 | github.com/autom8ter/dagger/v3/dagger.go:956.6,957.10 1 102 251 | github.com/autom8ter/dagger/v3/dagger.go:958.15,959.10 1 1 252 | github.com/autom8ter/dagger/v3/dagger.go:960.24,961.11 1 101 253 | github.com/autom8ter/dagger/v3/dagger.go:961.11,963.5 1 0 254 | github.com/autom8ter/dagger/v3/dagger.go:964.4,964.14 1 101 255 | github.com/autom8ter/dagger/v3/dagger.go:964.14,966.5 1 0 256 | github.com/autom8ter/dagger/v3/dagger.go:972.35,973.24 1 0 257 | github.com/autom8ter/dagger/v3/dagger.go:973.24,975.3 1 0 258 | github.com/autom8ter/dagger/v3/dagger.go:979.39,981.2 1 201 259 | github.com/autom8ter/dagger/v3/dagger.go:984.43,985.9 1 100 260 | github.com/autom8ter/dagger/v3/dagger.go:986.23,987.15 1 100 261 | github.com/autom8ter/dagger/v3/dagger.go:992.37,994.2 1 2 262 | github.com/autom8ter/dagger/v3/dagger.go:1003.34,1006.2 2 1 263 | github.com/autom8ter/dagger/v3/dagger.go:1009.30,1013.2 3 100 264 | github.com/autom8ter/dagger/v3/dagger.go:1016.36,1019.24 3 100 265 | github.com/autom8ter/dagger/v3/dagger.go:1019.24,1021.3 1 0 266 | github.com/autom8ter/dagger/v3/dagger.go:1021.8,1026.3 4 100 267 | github.com/autom8ter/dagger/v3/dagger.go:1030.30,1034.2 3 2 268 | github.com/autom8ter/dagger/v3/dagger.go:1037.37,1040.24 3 0 269 | github.com/autom8ter/dagger/v3/dagger.go:1040.24,1042.3 1 0 270 | github.com/autom8ter/dagger/v3/dagger.go:1043.2,1043.40 1 0 271 | github.com/autom8ter/dagger/v3/dagger.go:1047.51,1048.6 1 0 272 | github.com/autom8ter/dagger/v3/dagger.go:1048.6,1050.10 2 0 273 | github.com/autom8ter/dagger/v3/dagger.go:1050.10,1052.4 1 0 274 | github.com/autom8ter/dagger/v3/dagger.go:1053.3,1053.15 1 0 275 | github.com/autom8ter/dagger/v3/dagger.go:1053.15,1055.4 1 0 276 | github.com/autom8ter/dagger/v3/dagger.go:1061.76,1062.6 1 0 277 | github.com/autom8ter/dagger/v3/dagger.go:1062.6,1063.10 1 0 278 | github.com/autom8ter/dagger/v3/dagger.go:1064.15,1065.10 1 0 279 | github.com/autom8ter/dagger/v3/dagger.go:1066.11,1068.11 2 0 280 | github.com/autom8ter/dagger/v3/dagger.go:1068.11,1070.5 1 0 281 | github.com/autom8ter/dagger/v3/dagger.go:1071.4,1071.16 1 0 282 | github.com/autom8ter/dagger/v3/dagger.go:1071.16,1073.5 1 0 283 | github.com/autom8ter/dagger/v3/dagger.go:1079.34,1082.2 2 4 284 | github.com/autom8ter/dagger/v3/dagger.go:1092.76,1093.6 1 0 285 | github.com/autom8ter/dagger/v3/dagger.go:1093.6,1094.10 1 0 286 | github.com/autom8ter/dagger/v3/dagger.go:1095.15,1096.10 1 0 287 | github.com/autom8ter/dagger/v3/dagger.go:1097.11,1099.11 2 0 288 | github.com/autom8ter/dagger/v3/dagger.go:1099.11,1101.5 1 0 289 | github.com/autom8ter/dagger/v3/dagger.go:1102.4,1102.16 1 0 290 | github.com/autom8ter/dagger/v3/dagger.go:1102.16,1104.5 1 0 291 | github.com/autom8ter/dagger/v3/dagger.go:1110.30,1114.2 3 403 292 | github.com/autom8ter/dagger/v3/dagger.go:1117.36,1120.24 3 404 293 | github.com/autom8ter/dagger/v3/dagger.go:1120.24,1122.3 1 1 294 | github.com/autom8ter/dagger/v3/dagger.go:1122.8,1127.3 4 403 295 | github.com/autom8ter/dagger/v3/dagger.go:1131.28,1135.2 3 0 296 | github.com/autom8ter/dagger/v3/dagger.go:1138.51,1139.6 1 1 297 | github.com/autom8ter/dagger/v3/dagger.go:1139.6,1141.10 2 102 298 | github.com/autom8ter/dagger/v3/dagger.go:1141.10,1143.4 1 1 299 | github.com/autom8ter/dagger/v3/dagger.go:1144.3,1144.13 1 101 300 | github.com/autom8ter/dagger/v3/dagger.go:1144.13,1146.4 1 0 301 | github.com/autom8ter/dagger/v3/dagger.go:1151.33,1155.2 3 0 302 | github.com/autom8ter/dagger/v3/dagger.go:1158.59,1160.41 2 0 303 | github.com/autom8ter/dagger/v3/dagger.go:1160.41,1162.3 1 0 304 | github.com/autom8ter/dagger/v3/dagger.go:1163.2,1163.15 1 0 305 | github.com/autom8ter/dagger/v3/dagger.go:1167.30,1171.2 3 206 306 | github.com/autom8ter/dagger/v3/dagger.go:1174.37,1177.24 3 0 307 | github.com/autom8ter/dagger/v3/dagger.go:1177.24,1179.3 1 0 308 | github.com/autom8ter/dagger/v3/dagger.go:1179.8,1183.3 3 0 309 | github.com/autom8ter/dagger/v3/dagger.go:1193.37,1196.2 2 809 310 | github.com/autom8ter/dagger/v3/dagger.go:1199.29,1203.2 3 1508 311 | github.com/autom8ter/dagger/v3/dagger.go:1206.32,1210.2 3 302 312 | github.com/autom8ter/dagger/v3/dagger.go:1213.39,1218.2 4 1906 313 | github.com/autom8ter/dagger/v3/dagger.go:1221.49,1224.26 3 0 314 | github.com/autom8ter/dagger/v3/dagger.go:1224.26,1225.13 1 0 315 | github.com/autom8ter/dagger/v3/dagger.go:1225.13,1227.4 1 0 316 | github.com/autom8ter/dagger/v3/dagger.go:1232.28,1236.2 3 203 317 | github.com/autom8ter/dagger/v3/dagger.go:1239.31,1243.29 4 0 318 | github.com/autom8ter/dagger/v3/dagger.go:1243.29,1245.3 1 0 319 | github.com/autom8ter/dagger/v3/dagger.go:1246.2,1246.15 1 0 320 | github.com/autom8ter/dagger/v3/dagger.go:1250.57,1252.41 2 0 321 | github.com/autom8ter/dagger/v3/dagger.go:1252.41,1254.3 1 0 322 | github.com/autom8ter/dagger/v3/dagger.go:1255.2,1255.15 1 0 323 | github.com/autom8ter/dagger/v3/dagger.go:1273.67,1279.2 1 1 324 | github.com/autom8ter/dagger/v3/dagger.go:1282.60,1284.12 2 100 325 | github.com/autom8ter/dagger/v3/dagger.go:1284.12,1288.74 4 100 326 | github.com/autom8ter/dagger/v3/dagger.go:1288.74,1291.11 3 10000 327 | github.com/autom8ter/dagger/v3/dagger.go:1292.22,1293.17 1 0 328 | github.com/autom8ter/dagger/v3/dagger.go:1294.28,1295.16 1 0 329 | github.com/autom8ter/dagger/v3/dagger.go:1296.25,1297.16 1 10000 330 | github.com/autom8ter/dagger/v3/dagger.go:1301.2,1301.8 1 100 331 | github.com/autom8ter/dagger/v3/dagger.go:1306.65,1315.12 6 100 332 | github.com/autom8ter/dagger/v3/dagger.go:1315.12,1324.3 8 100 333 | github.com/autom8ter/dagger/v3/dagger.go:1325.2,1325.11 1 100 334 | github.com/autom8ter/dagger/v3/dagger.go:1329.37,1331.2 1 2 335 | github.com/autom8ter/dagger/v3/dagger.go:1334.35,1337.2 2 1 336 | github.com/autom8ter/dagger/v3/dagger.go:1348.57,1354.12 3 1 337 | github.com/autom8ter/dagger/v3/dagger.go:1354.12,1355.10 1 1 338 | github.com/autom8ter/dagger/v3/dagger.go:1356.19,1358.37 2 1 339 | github.com/autom8ter/dagger/v3/dagger.go:1358.37,1360.5 1 300 340 | github.com/autom8ter/dagger/v3/dagger.go:1361.4,1361.17 1 1 341 | github.com/autom8ter/dagger/v3/dagger.go:1364.2,1364.10 1 1 342 | github.com/autom8ter/dagger/v3/dagger.go:1369.73,1375.2 5 300 343 | github.com/autom8ter/dagger/v3/dagger.go:1378.33,1381.35 3 1 344 | github.com/autom8ter/dagger/v3/dagger.go:1381.35,1383.3 1 300 345 | github.com/autom8ter/dagger/v3/dagger.go:1384.2,1384.12 1 1 346 | github.com/autom8ter/dagger/v3/dagger.go:1397.47,1407.2 5 1 347 | github.com/autom8ter/dagger/v3/dagger.go:1410.35,1412.2 1 4 348 | github.com/autom8ter/dagger/v3/dagger.go:1415.46,1416.9 1 0 349 | github.com/autom8ter/dagger/v3/dagger.go:1417.27,1418.10 1 0 350 | github.com/autom8ter/dagger/v3/dagger.go:1418.10,1420.4 1 0 351 | github.com/autom8ter/dagger/v3/dagger.go:1421.3,1421.21 1 0 352 | github.com/autom8ter/dagger/v3/dagger.go:1422.10,1423.20 1 0 353 | github.com/autom8ter/dagger/v3/dagger.go:1428.70,1431.9 3 1 354 | github.com/autom8ter/dagger/v3/dagger.go:1432.27,1433.10 1 0 355 | github.com/autom8ter/dagger/v3/dagger.go:1433.10,1435.4 1 0 356 | github.com/autom8ter/dagger/v3/dagger.go:1436.3,1436.20 1 0 357 | github.com/autom8ter/dagger/v3/dagger.go:1437.20,1438.24 1 1 358 | github.com/autom8ter/dagger/v3/dagger.go:1445.44,1448.23 3 4 359 | github.com/autom8ter/dagger/v3/dagger.go:1448.23,1450.3 1 0 360 | github.com/autom8ter/dagger/v3/dagger.go:1451.2,1451.19 1 4 361 | github.com/autom8ter/dagger/v3/dagger.go:1451.19,1453.3 1 1 362 | github.com/autom8ter/dagger/v3/dagger.go:1454.2,1456.12 3 3 363 | github.com/autom8ter/dagger/v3/dagger.go:1460.33,1462.2 1 1 364 | github.com/autom8ter/dagger/v3/dagger.go:1465.45,1469.2 3 1 365 | github.com/autom8ter/dagger/v3/dagger.go:1472.43,1474.9 2 0 366 | github.com/autom8ter/dagger/v3/dagger.go:1474.9,1476.3 1 0 367 | github.com/autom8ter/dagger/v3/dagger.go:1477.2,1478.25 2 0 368 | github.com/autom8ter/dagger/v3/dagger.go:1483.37,1485.19 2 1 369 | github.com/autom8ter/dagger/v3/dagger.go:1485.19,1488.20 3 1 370 | github.com/autom8ter/dagger/v3/dagger.go:1488.20,1491.4 2 0 371 | github.com/autom8ter/dagger/v3/dagger.go:1492.3,1492.17 1 1 372 | github.com/autom8ter/dagger/v3/dagger.go:1492.17,1496.4 3 1 373 | github.com/autom8ter/dagger/v3/dagger.go:1498.2,1498.12 1 1 374 | github.com/autom8ter/dagger/v3/dagger.go:1503.46,1504.37 1 0 375 | github.com/autom8ter/dagger/v3/dagger.go:1504.37,1507.3 2 0 376 | -------------------------------------------------------------------------------- /dagger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package dagger is a collection of generic, concurrency safe datastructures including a Directed Acyclic Graph and others. 3 | Datastructures are implemented using generics in Go 1.18. 4 | 5 | Supported Datastructures: 6 | 7 | DAG: thread safe directed acyclic graph 8 | 9 | Queue: unbounded thread safe fifo queue 10 | 11 | Stack: unbounded thread safe lifo stack 12 | 13 | BoundedQueue: bounded thread safe fifo queue with a fixed capacity 14 | 15 | PriorityQueue: thread safe priority queue 16 | 17 | HashMap: thread safe hashmap 18 | 19 | Set: thread safe set 20 | 21 | ChannelGroup: thread safe group of channels for broadcasting 1 value to N channels 22 | 23 | MultiContext: thread safe context for coordinating the cancellation of multiple contexts 24 | 25 | Borrower: thread safe object ownership manager 26 | */ 27 | package dagger 28 | 29 | import ( 30 | "context" 31 | "crypto/rand" 32 | "encoding/hex" 33 | "fmt" 34 | "image" 35 | "reflect" 36 | "sort" 37 | "strconv" 38 | "strings" 39 | "sync" 40 | "time" 41 | 42 | "github.com/goccy/go-graphviz" 43 | "github.com/goccy/go-graphviz/cgraph" 44 | "golang.org/x/sync/errgroup" 45 | 46 | "github.com/autom8ter/async" 47 | ) 48 | 49 | // UniqueID returns a unique identifier with the given prefix 50 | func UniqueID(prefix string) string { 51 | b := make([]byte, 4) 52 | _, err := rand.Read(b) 53 | if err != nil { 54 | panic(err) 55 | } 56 | if prefix == "" { 57 | prefix = "id" 58 | } 59 | return fmt.Sprintf("%s-%s", prefix, hex.EncodeToString(b)) 60 | } 61 | 62 | // GraphEdge is a relationship between two nodes 63 | type GraphEdge[T Node] struct { 64 | // ID is the unique identifier of the edge 65 | id string 66 | // Metadata is the metadata of the edge 67 | metadata map[string]string 68 | // From returns the root node of the edge 69 | from *GraphNode[T] 70 | // To returns the target node of the edge 71 | to *GraphNode[T] 72 | // Relationship is the relationship between the two nodes 73 | relationship string 74 | edge *cgraph.Edge 75 | } 76 | 77 | // ID returns the unique identifier of the node 78 | func (n *GraphEdge[T]) ID() string { 79 | return n.id 80 | } 81 | 82 | // Metadata returns the metadata of the node 83 | func (n *GraphEdge[T]) Metadata() map[string]string { 84 | return n.metadata 85 | } 86 | 87 | // From returns the from node of the edge 88 | func (n *GraphEdge[T]) From() *GraphNode[T] { 89 | return n.from 90 | } 91 | 92 | // To returns the to node of the edge 93 | func (n *GraphEdge[T]) To() *GraphNode[T] { 94 | return n.to 95 | } 96 | 97 | // Relationship returns the relationship between the two nodes 98 | func (n *GraphEdge[T]) Relationship() string { 99 | return n.relationship 100 | } 101 | 102 | // SetMetadata sets the metadata of the node 103 | func (n *GraphEdge[T]) SetMetadata(metadata map[string]string) { 104 | for k, v := range metadata { 105 | n.metadata[k] = v 106 | } 107 | } 108 | 109 | // Node is a node in the graph. It can be connected to other nodes via edges. 110 | type Node interface { 111 | // ID returns the unique identifier of the node 112 | ID() string 113 | // Metadata returns the metadata of the node 114 | Metadata() map[string]string 115 | // SetMetadata sets the metadata of the node 116 | SetMetadata(metadata map[string]string) 117 | } 118 | 119 | // GraphNode is a node in the graph. It can be connected to other nodes via edges. 120 | type GraphNode[T Node] struct { 121 | Node 122 | edgesFrom *HashMap[string, *GraphEdge[T]] 123 | edgesTo *HashMap[string, *GraphEdge[T]] 124 | graph *DAG[T] 125 | node *cgraph.Node 126 | } 127 | 128 | // DFS performs a depth-first search on the graph starting from the current node 129 | func (n *GraphNode[T]) DFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error { 130 | return n.graph.DFS(ctx, reverse, n, fn) 131 | } 132 | 133 | // BFS performs a breadth-first search on the graph starting from the current node 134 | func (n *GraphNode[T]) BFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error { 135 | return n.graph.BFS(ctx, reverse, n, fn) 136 | } 137 | 138 | // EdgesFrom iterates over the edges from the current node to other nodes with the given relationship. 139 | // If the relationship is empty, all relationships will be iterated over. 140 | func (n *GraphNode[T]) EdgesFrom(relationship string, fn func(e *GraphEdge[T]) bool) { 141 | n.graph.mu.RLock() 142 | defer n.graph.mu.RUnlock() 143 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 144 | if relationship != "" && edge.Relationship() != relationship { 145 | return true 146 | } 147 | return fn(edge) 148 | }) 149 | } 150 | 151 | // EdgesTo iterates over the edges from other nodes to the current node with the given relationship. 152 | // If the relationship is empty, all relationships will be iterated over. 153 | func (n *GraphNode[T]) EdgesTo(relationship string, fn func(e *GraphEdge[T]) bool) { 154 | n.graph.mu.RLock() 155 | defer n.graph.mu.RUnlock() 156 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 157 | if relationship != "" && edge.Relationship() != relationship { 158 | return true 159 | } 160 | return fn(edge) 161 | }) 162 | } 163 | 164 | // SetEdge sets an edge from the current node to the node with the given nodeID. 165 | // If the nodeID does not exist, an error is returned. 166 | // If the edgeID is empty, a unique id will be generated. 167 | // If the metadata is nil, an empty map will be used. 168 | func (n *GraphNode[T]) SetEdge(relationship string, toNode Node, metadata map[string]string) (*GraphEdge[T], error) { 169 | if metadata == nil { 170 | metadata = make(map[string]string) 171 | } 172 | to, ok := n.graph.nodes.Get(toNode.ID()) 173 | if !ok { 174 | to = n.graph.SetNode(toNode) 175 | } 176 | n.graph.mu.Lock() 177 | defer n.graph.mu.Unlock() 178 | e := &GraphEdge[T]{ 179 | id: strings.ReplaceAll(strings.ToLower(fmt.Sprintf("%v-(%v)-%v", n.ID(), relationship, toNode.ID())), " ", "-"), 180 | metadata: metadata, 181 | from: n, 182 | to: to, 183 | } 184 | n.graph.edges.Set(e.ID(), e) 185 | to.edgesTo.Set(e.ID(), e) 186 | n.edgesFrom.Set(e.ID(), e) 187 | if n.graph.options.vizualize { 188 | ge, err := n.graph.viz.CreateEdge(e.ID(), n.node, to.node) 189 | if err != nil { 190 | return nil, err 191 | } 192 | ge.SetLabel(e.ID()) 193 | if label, ok := metadata["label"]; ok { 194 | ge.SetLabel(label) 195 | } 196 | if color, ok := metadata["color"]; ok { 197 | ge.SetColor(color) 198 | } 199 | if fontColor, ok := metadata["fontcolor"]; ok { 200 | ge.SetFontColor(fontColor) 201 | } 202 | if weight, ok := metadata["weight"]; ok { 203 | weightFloat, _ := strconv.ParseFloat(weight, 64) 204 | ge.SetWeight(weightFloat) 205 | } 206 | if penWidth, ok := metadata["penwidth"]; ok { 207 | penWidthFloat, _ := strconv.ParseFloat(penWidth, 64) 208 | ge.SetPenWidth(penWidthFloat) 209 | } 210 | e.edge = ge 211 | } 212 | return e, nil 213 | } 214 | 215 | // RemoveEdge removes an edge from the current node by edgeID 216 | func (n *GraphNode[T]) RemoveEdge(edgeID string) { 217 | n.graph.mu.Lock() 218 | defer n.graph.mu.Unlock() 219 | n.removeEdge(edgeID) 220 | } 221 | 222 | func (n *GraphNode[T]) removeEdge(edgeID string) { 223 | edge, ok := n.graph.edges.Get(edgeID) 224 | if !ok { 225 | return 226 | } 227 | n.graph.edges.Delete(edgeID) 228 | n.edgesFrom.Delete(edgeID) 229 | n.edgesTo.Delete(edgeID) 230 | if edge.edge != nil { 231 | n.graph.viz.DeleteEdge(edge.edge) 232 | } 233 | } 234 | 235 | // Remove removes the current node from the graph 236 | func (n *GraphNode[T]) Remove() error { 237 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 238 | n.removeEdge(edge.ID()) 239 | return true 240 | }) 241 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 242 | n.removeEdge(edge.ID()) 243 | return true 244 | }) 245 | n.graph.nodes.Delete(n.ID()) 246 | if n.graph.options.vizualize { 247 | n.graph.viz.DeleteNode(n.node) 248 | } 249 | return nil 250 | } 251 | 252 | // DirectedGraph returns the graph the node belongs to 253 | func (n *GraphNode[T]) Graph() *DAG[T] { 254 | return n.graph 255 | } 256 | 257 | // Ancestors returns the ancestors of the current node 258 | func (n *GraphNode[T]) Ancestors(fn func(node *GraphNode[T]) bool) { 259 | n.graph.mu.RLock() 260 | defer n.graph.mu.RUnlock() 261 | visited := NewSet[string]() 262 | n.ancestors(visited, fn) 263 | } 264 | 265 | func (n *GraphNode[T]) ancestors(visited *Set[string], fn func(node *GraphNode[T]) bool) { 266 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 267 | if visited.Contains(edge.From().ID()) { 268 | return true 269 | } 270 | visited.Add(edge.From().ID()) 271 | if !fn(edge.From()) { 272 | return false 273 | } 274 | edge.From().ancestors(visited, fn) 275 | return true 276 | }) 277 | } 278 | 279 | // Descendants returns the descendants of the current node 280 | func (n *GraphNode[T]) Descendants(fn func(node *GraphNode[T]) bool) { 281 | n.graph.mu.RLock() 282 | defer n.graph.mu.RUnlock() 283 | visited := NewSet[string]() 284 | n.descendants(visited, fn) 285 | } 286 | 287 | func (n *GraphNode[T]) descendants(visited *Set[string], fn func(node *GraphNode[T]) bool) { 288 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 289 | if visited.Contains(edge.To().ID()) { 290 | return true 291 | } 292 | visited.Add(edge.To().ID()) 293 | if !fn(edge.To()) { 294 | return false 295 | } 296 | edge.To().descendants(visited, fn) 297 | return true 298 | }) 299 | } 300 | 301 | // IsConnectedTo returns true if the current node is connected to the given node in any direction 302 | func (n *GraphNode[T]) IsConnectedTo(node *GraphNode[T]) bool { 303 | var result bool 304 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 305 | if edge.To() == node { 306 | result = true 307 | return false 308 | } 309 | return true 310 | }) 311 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 312 | if edge.From() == node { 313 | result = true 314 | return false 315 | } 316 | return true 317 | }) 318 | return result 319 | } 320 | 321 | // DAG is a concurrency safe, mutable, in-memory directed graph 322 | type DAG[T Node] struct { 323 | nodes *HashMap[string, *GraphNode[T]] 324 | edges *HashMap[string, *GraphEdge[T]] 325 | gviz *graphviz.Graphviz 326 | viz *cgraph.Graph 327 | mu sync.RWMutex 328 | options *dagOpts 329 | } 330 | 331 | type dagOpts struct { 332 | vizualize bool 333 | } 334 | 335 | // DagOpt is an option for configuring a DAG 336 | type DagOpt func(*dagOpts) 337 | 338 | // WithVizualization enables graphviz visualization on the DAG 339 | func WithVizualization() DagOpt { 340 | return func(opts *dagOpts) { 341 | opts.vizualize = true 342 | } 343 | } 344 | 345 | // NewDAG creates a new Directed Acyclic Graph instance 346 | func NewDAG[T Node](opts ...DagOpt) (*DAG[T], error) { 347 | var err error 348 | options := &dagOpts{} 349 | for _, opt := range opts { 350 | opt(options) 351 | } 352 | g := &DAG[T]{ 353 | nodes: NewHashMap[string, *GraphNode[T]](), 354 | edges: NewHashMap[string, *GraphEdge[T]](), 355 | gviz: graphviz.New(), 356 | options: options, 357 | } 358 | if options.vizualize { 359 | graph, _ := g.gviz.Graph() 360 | g.viz = graph 361 | } 362 | return g, err 363 | } 364 | 365 | // SetNode sets a node in the graph - it will use the node's ID as the key and overwrite any existing node with the same ID 366 | func (g *DAG[T]) SetNode(node Node) *GraphNode[T] { 367 | n := &GraphNode[T]{ 368 | Node: node, 369 | edgesTo: NewHashMap[string, *GraphEdge[T]](), 370 | edgesFrom: NewHashMap[string, *GraphEdge[T]](), 371 | graph: g, 372 | } 373 | g.nodes.Set(node.ID(), n) 374 | if g.options.vizualize { 375 | gn, err := g.viz.CreateNode(fmt.Sprintf("%v", node.ID())) 376 | if err != nil { 377 | panic(err) 378 | } 379 | gn.SetLabel(fmt.Sprintf("%v", node.ID())) 380 | if label, ok := node.Metadata()["label"]; ok { 381 | gn.SetLabel(label) 382 | } 383 | if color, ok := node.Metadata()["color"]; ok { 384 | gn.SetColor(color) 385 | } 386 | 387 | n.node = gn 388 | } 389 | 390 | return n 391 | } 392 | 393 | // HasNode returns true if the node with the given id exists in the graph 394 | func (g *DAG[T]) HasNode(id string) bool { 395 | _, ok := g.nodes.Get(id) 396 | return ok 397 | } 398 | 399 | // HasEdge returns true if the edge with the given id exists in the graph 400 | func (g *DAG[T]) HasEdge(id string) bool { 401 | _, ok := g.edges.Get(id) 402 | return ok 403 | } 404 | 405 | // GetNode returns the node with the given id 406 | func (g *DAG[T]) GetNode(id string) (*GraphNode[T], bool) { 407 | val, ok := g.nodes.Get(id) 408 | return val, ok 409 | } 410 | 411 | // Size returns the number of nodes and edges in the graph 412 | func (g *DAG[T]) Size() (int, int) { 413 | return g.nodes.Len(), g.edges.Len() 414 | } 415 | 416 | // GetNodes returns all nodes in the graph 417 | func (g *DAG[T]) GetNodes() []*GraphNode[T] { 418 | nodes := make([]*GraphNode[T], 0, g.nodes.Len()) 419 | g.nodes.Range(func(key string, val *GraphNode[T]) bool { 420 | nodes = append(nodes, val) 421 | return true 422 | }) 423 | return nodes 424 | } 425 | 426 | // GetEdges returns all edges in the graph 427 | func (g *DAG[T]) GetEdges() []*GraphEdge[T] { 428 | edges := make([]*GraphEdge[T], 0, g.edges.Len()) 429 | g.edges.Range(func(key string, val *GraphEdge[T]) bool { 430 | edges = append(edges, val) 431 | return true 432 | }) 433 | return edges 434 | } 435 | 436 | // GetEdge returns the edge with the given id 437 | func (g *DAG[T]) GetEdge(id string) (*GraphEdge[T], bool) { 438 | val, ok := g.edges.Get(id) 439 | return val, ok 440 | } 441 | 442 | // RangeEdges iterates over all edges in the graph 443 | func (g *DAG[T]) RangeEdges(fn func(e *GraphEdge[T]) bool) { 444 | g.edges.Range(func(key string, val *GraphEdge[T]) bool { 445 | return fn(val) 446 | }) 447 | } 448 | 449 | // RangeNodes iterates over all nodes in the graph 450 | func (g *DAG[T]) RangeNodes(fn func(n *GraphNode[T]) bool) { 451 | g.nodes.Range(func(key string, val *GraphNode[T]) bool { 452 | return fn(val) 453 | }) 454 | } 455 | 456 | // GraphSearchFunc is a function that is called on each node in the graph during a search 457 | type GraphSearchFunc[T Node] func(ctx context.Context, relationship string, node *GraphNode[T]) bool 458 | 459 | // BFS executes a depth first search on the graph starting from the current node. 460 | // The reverse parameter determines whether the search is reversed or not. 461 | // The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped. 462 | func (g *DAG[T]) BFS(ctx context.Context, reverse bool, start *GraphNode[T], search GraphSearchFunc[T]) error { 463 | var visited = NewSet[string]() 464 | stack := NewStack[*searchItem[T]]() 465 | ctx, cancel := context.WithCancel(ctx) 466 | defer cancel() 467 | if err := g.breadthFirstSearch(ctx, &breadthFirstSearchState[T]{ 468 | visited: visited, 469 | stack: stack, 470 | reverse: reverse, 471 | root: start, 472 | next: start, 473 | relationship: "", 474 | }); err != nil { 475 | return err 476 | } 477 | stack.Range(func(element *searchItem[T]) bool { 478 | if element.node.ID() != start.ID() { 479 | return search(ctx, element.relationship, element.node) 480 | } 481 | return true 482 | }) 483 | return nil 484 | } 485 | 486 | // DFS executes a depth first search on the graph starting from the current node. 487 | // The reverse parameter determines whether the search is reversed or not. 488 | // The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped. 489 | func (g *DAG[T]) DFS(ctx context.Context, reverse bool, start *GraphNode[T], fn GraphSearchFunc[T]) error { 490 | ctx, cancel := context.WithCancel(ctx) 491 | var visited = NewSet[string]() 492 | queue := NewBoundedQueue[*searchItem[T]](0) 493 | egp1, ctx := errgroup.WithContext(ctx) 494 | egp1.Go(func() error { 495 | egp2, ctx := errgroup.WithContext(ctx) 496 | if err := g.depthFirstSearch(ctx, &depthFirstSearchState[T]{ 497 | visited: visited, 498 | egp: egp2, 499 | queue: queue, 500 | reverse: reverse, 501 | root: start, 502 | next: start, 503 | relationship: "", 504 | }); err != nil { 505 | return err 506 | } 507 | if err := egp2.Wait(); err != nil { 508 | return err 509 | } 510 | cancel() 511 | return nil 512 | }) 513 | egp1.Go(func() error { 514 | queue.RangeContext(ctx, func(element *searchItem[T]) bool { 515 | if element.node.ID() != start.ID() { 516 | return fn(ctx, element.relationship, element.node) 517 | } 518 | return true 519 | }) 520 | return nil 521 | }) 522 | if err := egp1.Wait(); err != nil { 523 | return err 524 | } 525 | return nil 526 | } 527 | 528 | // searchItem is an item that is used in the search queue/stack in DFS/BFS 529 | type searchItem[T Node] struct { 530 | node *GraphNode[T] 531 | relationship string 532 | } 533 | 534 | type breadthFirstSearchState[T Node] struct { 535 | visited *Set[string] 536 | stack *Stack[*searchItem[T]] 537 | reverse bool 538 | root *GraphNode[T] 539 | next *GraphNode[T] 540 | relationship string 541 | } 542 | 543 | func (g *DAG[T]) breadthFirstSearch(ctx context.Context, state *breadthFirstSearchState[T]) error { 544 | if ctx.Err() != nil { 545 | return ctx.Err() 546 | } 547 | if state.next == nil { 548 | state.next = state.root 549 | } 550 | if !state.visited.Contains(state.next.ID()) { 551 | state.visited.Add(state.next.ID()) 552 | state.stack.Push(&searchItem[T]{ 553 | node: state.next, 554 | relationship: state.relationship, 555 | }) 556 | if state.reverse { 557 | state.next.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 558 | g.breadthFirstSearch(ctx, &breadthFirstSearchState[T]{ 559 | stack: state.stack, 560 | reverse: state.reverse, 561 | root: state.root, 562 | next: edge.From(), 563 | relationship: edge.Relationship(), 564 | visited: state.visited, 565 | }) 566 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil 567 | }) 568 | } else { 569 | state.next.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 570 | g.breadthFirstSearch(ctx, &breadthFirstSearchState[T]{ 571 | visited: state.visited, 572 | stack: state.stack, 573 | reverse: state.reverse, 574 | root: state.root, 575 | next: edge.To(), 576 | relationship: edge.Relationship(), 577 | }) 578 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil 579 | }) 580 | } 581 | } 582 | return ctx.Err() 583 | } 584 | 585 | type depthFirstSearchState[T Node] struct { 586 | visited *Set[string] 587 | egp *errgroup.Group 588 | queue *BoundedQueue[*searchItem[T]] 589 | reverse bool 590 | root *GraphNode[T] 591 | next *GraphNode[T] 592 | relationship string 593 | } 594 | 595 | func (g *DAG[T]) depthFirstSearch(ctx context.Context, state *depthFirstSearchState[T]) error { 596 | if ctx.Err() != nil { 597 | return ctx.Err() 598 | } 599 | if state.next == nil { 600 | state.next = state.root 601 | } 602 | if !state.visited.Contains(state.next.ID()) { 603 | state.visited.Add(state.next.ID()) 604 | state.queue.Push(&searchItem[T]{ 605 | node: state.next, 606 | relationship: state.relationship, 607 | }) 608 | if state.reverse { 609 | state.egp.Go(func() error { 610 | state.next.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 611 | g.depthFirstSearch(ctx, &depthFirstSearchState[T]{ 612 | visited: state.visited, 613 | egp: state.egp, 614 | queue: state.queue, 615 | reverse: state.reverse, 616 | root: state.root, 617 | next: edge.From(), 618 | relationship: edge.Relationship(), 619 | }) 620 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil 621 | }) 622 | return nil 623 | }) 624 | } else { 625 | state.egp.Go(func() error { 626 | state.next.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 627 | g.depthFirstSearch(ctx, &depthFirstSearchState[T]{ 628 | visited: state.visited, 629 | egp: state.egp, 630 | queue: state.queue, 631 | reverse: state.reverse, 632 | root: state.root, 633 | next: edge.To(), 634 | relationship: edge.Relationship(), 635 | }) 636 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil 637 | }) 638 | return nil 639 | }) 640 | } 641 | } 642 | return ctx.Err() 643 | } 644 | 645 | // Acyclic returns true if the graph contains no cycles. 646 | func (g *DAG[T]) Acyclic() bool { 647 | isAcyclic := true 648 | g.nodes.Range(func(key string, node *GraphNode[T]) bool { 649 | if node.edgesFrom.Len() > 0 { 650 | visited := NewSet[string]() 651 | onStack := NewSet[string]() 652 | if g.isCyclic(node, visited, onStack) { 653 | isAcyclic = false 654 | return false 655 | } 656 | } 657 | return true 658 | }) 659 | return isAcyclic 660 | } 661 | 662 | // isAcyclic returns true if the graph contains no cycles. 663 | func (g *DAG[T]) isCyclic(node *GraphNode[T], visited *Set[string], onStack *Set[string]) bool { 664 | visited.Add(node.ID()) 665 | onStack.Add(node.ID()) 666 | result := false 667 | node.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 668 | if visited.Contains(edge.To().ID()) { 669 | if g.isCyclic(edge.To(), visited, onStack) { 670 | result = true 671 | return false 672 | } 673 | } else if onStack.Contains(edge.To().ID()) { 674 | result = true 675 | return false 676 | } 677 | return true 678 | }) 679 | return result 680 | } 681 | 682 | func (g *DAG[T]) TopologicalSort(reverse bool) ([]*GraphNode[T], error) { 683 | if !g.Acyclic() { 684 | return nil, fmt.Errorf("topological sort cannot be computed on cyclical graph") 685 | } 686 | stack := NewStack[*GraphNode[T]]() 687 | permanent := NewSet[string]() 688 | temporary := NewSet[string]() 689 | g.nodes.Range(func(key string, node *GraphNode[T]) bool { 690 | g.topology(true, stack, node, permanent, temporary) 691 | return true 692 | }) 693 | var sorted []*GraphNode[T] 694 | for stack.Len() > 0 { 695 | val, _ := stack.Pop() 696 | sorted = append(sorted, val) 697 | } 698 | if reverse { 699 | for i, j := 0, len(sorted)-1; i < j; i, j = i+1, j-1 { 700 | sorted[i], sorted[j] = sorted[j], sorted[i] 701 | } 702 | } 703 | return sorted, nil 704 | } 705 | 706 | func (g *DAG[T]) topology(reverse bool, stack *Stack[*GraphNode[T]], node *GraphNode[T], permanent, temporary *Set[string]) { 707 | if permanent.Contains(node.ID()) { 708 | return 709 | } 710 | if temporary.Contains(node.ID()) { 711 | panic("not a DAG") 712 | } 713 | temporary.Add(node.ID()) 714 | if reverse { 715 | node.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool { 716 | g.topology(reverse, stack, edge.From(), permanent, temporary) 717 | return true 718 | }) 719 | } else { 720 | node.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool { 721 | g.topology(reverse, stack, edge.From(), permanent, temporary) 722 | return true 723 | }) 724 | } 725 | temporary.Remove(node.ID()) 726 | permanent.Add(node.ID()) 727 | stack.Push(node) 728 | } 729 | 730 | // GraphViz returns a graphviz image 731 | func (g *DAG[T]) GraphViz() (image.Image, error) { 732 | if g.viz == nil { 733 | return nil, fmt.Errorf("graphviz not configured") 734 | } 735 | g.mu.RLock() 736 | defer g.mu.RUnlock() 737 | img, err := g.gviz.RenderImage(g.viz) 738 | if err != nil { 739 | return nil, err 740 | } 741 | return img, nil 742 | } 743 | 744 | // NewHashMap creates a new generic hash map 745 | func NewHashMap[K comparable, V any]() *HashMap[K, V] { 746 | return &HashMap[K, V]{ 747 | data: sync.Map{}, 748 | } 749 | } 750 | 751 | // HashMap is a thread safe map 752 | type HashMap[K comparable, V any] struct { 753 | data sync.Map 754 | } 755 | 756 | // Len returns the length of the map 757 | func (n *HashMap[K, V]) Len() int { 758 | count := 0 759 | n.data.Range(func(key, value interface{}) bool { 760 | count++ 761 | return true 762 | }) 763 | return count 764 | } 765 | 766 | // Get gets the value from the key 767 | func (n *HashMap[K, V]) Get(key K) (V, bool) { 768 | c, ok := n.data.Load(key) 769 | if !ok { 770 | return *new(V), ok 771 | } 772 | return c.(V), ok 773 | } 774 | 775 | // Set sets the key to the value 776 | func (n *HashMap[K, V]) Set(key K, value V) { 777 | n.data.Store(key, value) 778 | } 779 | 780 | // Delete deletes the key from the map 781 | func (n *HashMap[K, V]) Delete(key K) { 782 | n.data.Delete(key) 783 | } 784 | 785 | // Exists returns true if the key exists in the map 786 | func (n *HashMap[K, V]) Exists(key K) bool { 787 | _, ok := n.Get(key) 788 | return ok 789 | } 790 | 791 | // Clear clears the map 792 | func (n *HashMap[K, V]) Clear() { 793 | n.data.Range(func(key, value interface{}) bool { 794 | n.data.Delete(key) 795 | return true 796 | }) 797 | } 798 | 799 | // Keys returns a copy of the keys in the map as a slice 800 | func (n *HashMap[K, V]) Keys() []K { 801 | var keys []K 802 | n.data.Range(func(key, value interface{}) bool { 803 | keys = append(keys, key.(K)) 804 | return true 805 | }) 806 | return keys 807 | } 808 | 809 | // Values returns a copy of the values in the map as a slice 810 | func (n *HashMap[K, V]) Values() []V { 811 | var values []V 812 | n.data.Range(func(key, value interface{}) bool { 813 | values = append(values, value.(V)) 814 | return true 815 | }) 816 | return values 817 | } 818 | 819 | // Range ranges over the map with a function until false is returned 820 | func (n *HashMap[K, V]) Range(f func(key K, value V) bool) { 821 | n.data.Range(func(key, value interface{}) bool { 822 | return f(key.(K), value.(V)) 823 | }) 824 | } 825 | 826 | // Filter returns a new hashmap with the values that return true from the function 827 | func (n *HashMap[K, V]) Filter(f func(key K, value V) bool) *HashMap[K, V] { 828 | filtered := NewHashMap[K, V]() 829 | n.data.Range(func(key, value interface{}) bool { 830 | if f(key.(K), value.(V)) { 831 | filtered.Set(key.(K), value.(V)) 832 | } 833 | return true 834 | }) 835 | return filtered 836 | } 837 | 838 | // Map returns a copy of the hashmap as a map[string]T 839 | func (n *HashMap[K, V]) Map() map[K]V { 840 | copied := map[K]V{} 841 | n.data.Range(func(key, value interface{}) bool { 842 | copied[key.(K)] = value.(V) 843 | return true 844 | }) 845 | return copied 846 | } 847 | 848 | // priorityQueueItem is an item in the priority queue 849 | type priorityQueueItem[T any] struct { 850 | value T 851 | priority float64 852 | } 853 | 854 | // PriorityQueue is a thread safe priority queue 855 | type PriorityQueue[T any] struct { 856 | items []*priorityQueueItem[T] 857 | mu sync.RWMutex 858 | } 859 | 860 | // NewPriorityQueue creates a new priority queue 861 | func NewPriorityQueue[T any]() *PriorityQueue[T] { 862 | return &PriorityQueue[T]{ 863 | items: []*priorityQueueItem[T]{}, 864 | mu: sync.RWMutex{}, 865 | } 866 | } 867 | 868 | func (q *PriorityQueue[T]) UpdatePriority(value T, priority float64) { 869 | q.mu.Lock() 870 | defer q.mu.Unlock() 871 | for i, item := range q.items { 872 | if reflect.DeepEqual(item.value, value) { 873 | q.items[i].priority = priority 874 | sort.Slice(q.items, func(i, j int) bool { 875 | return q.items[i].priority < q.items[j].priority 876 | }) 877 | return 878 | } 879 | } 880 | } 881 | 882 | // Len returns the length of the queue 883 | func (q *PriorityQueue[T]) Len() int { 884 | q.mu.RLock() 885 | defer q.mu.RUnlock() 886 | return len(q.items) 887 | } 888 | 889 | // Push pushes an item onto the queue 890 | func (q *PriorityQueue[T]) Push(item T, weight float64) { 891 | q.mu.Lock() 892 | defer q.mu.Unlock() 893 | q.items = append(q.items, &priorityQueueItem[T]{value: item, priority: weight}) 894 | sort.Slice(q.items, func(i, j int) bool { 895 | return q.items[i].priority < q.items[j].priority 896 | }) 897 | } 898 | 899 | // Pop pops an item off the queue 900 | func (q *PriorityQueue[T]) Pop() (T, bool) { 901 | q.mu.Lock() 902 | defer q.mu.Unlock() 903 | if len(q.items) == 0 { 904 | return *new(T), false 905 | } 906 | item := q.items[0] 907 | q.items = q.items[1:] 908 | return item.value, true 909 | } 910 | 911 | // Peek returns the next item in the queue without removing it 912 | func (q *PriorityQueue[T]) Peek() (T, bool) { 913 | q.mu.RLock() 914 | defer q.mu.RUnlock() 915 | if len(q.items) == 0 { 916 | return *new(T), false 917 | } 918 | return q.items[0].value, true 919 | } 920 | 921 | // BoundedQueue is a basic FIFO BoundedQueue based on a buffered channel 922 | type BoundedQueue[T any] struct { 923 | closeOnce sync.Once 924 | ch *async.Channel[T] 925 | } 926 | 927 | // NewBoundedQueue returns a new BoundedQueue with the given max size. When the max size is reached, the queue will block until a value is removed. 928 | // If maxSize is 0, the queue will always block until a value is removed. The BoundedQueue is concurrent-safe. 929 | func NewBoundedQueue[T any](maxSize int) *BoundedQueue[T] { 930 | return &BoundedQueue[T]{ch: async.NewChannel[T](context.Background(), async.WithBufferSize[T](maxSize))} 931 | } 932 | 933 | // Range executes a provided function once for each BoundedQueue element until it returns false. 934 | func (q *BoundedQueue[T]) Range(fn func(element T) bool) { 935 | for { 936 | value, ok := q.ch.Recv(context.Background()) 937 | if !ok { 938 | return 939 | } 940 | if !fn(value) { 941 | return 942 | } 943 | } 944 | } 945 | 946 | // RangeContext executes a provided function once for each BoundedQueue element until it returns false or a value is sent to the done channel. 947 | // Use this function when you want to continuously process items from the queue until a done signal is received. 948 | func (q *BoundedQueue[T]) RangeContext(ctx context.Context, fn func(element T) bool) { 949 | for { 950 | value, ok := q.ch.Recv(ctx) 951 | if !ok { 952 | return 953 | } 954 | if !fn(value) { 955 | return 956 | } 957 | } 958 | } 959 | 960 | // Close closes the BoundedQueue channel. 961 | func (q *BoundedQueue[T]) Close() { 962 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) 963 | defer cancel() 964 | q.ch.Close(ctx) 965 | } 966 | 967 | // Push adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added. 968 | // If the queue is full, it will block until an element is removed. 969 | func (q *BoundedQueue[T]) Push(val T) bool { 970 | return q.ch.Send(context.Background(), val) 971 | } 972 | 973 | // PushContext adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added. 974 | // If the queue is full, it will block until an element is removed or the context is cancelled. 975 | func (q *BoundedQueue[T]) PushContext(ctx context.Context, val T) bool { 976 | return q.ch.Send(ctx, val) 977 | } 978 | 979 | // Pop removes and returns an element from the beginning of the BoundedQueue. 980 | func (q *BoundedQueue[T]) Pop() (T, bool) { 981 | return q.ch.Recv(context.Background()) 982 | } 983 | 984 | // PopContext removes and returns an element from the beginning of the BoundedQueue. 985 | // If no element is available, it will block until an element is available or the context is cancelled. 986 | func (q *BoundedQueue[T]) PopContext(ctx context.Context) (T, bool) { 987 | return q.ch.Recv(ctx) 988 | } 989 | 990 | // Len returns the number of elements in the BoundedQueue. 991 | func (q *BoundedQueue[T]) Len() int { 992 | return q.ch.Len() 993 | } 994 | 995 | // Queue is a thread safe non-blocking queue 996 | type Queue[T any] struct { 997 | mu sync.RWMutex 998 | values []T 999 | } 1000 | 1001 | // NewQueue returns a new Queue 1002 | func NewQueue[T any]() *Queue[T] { 1003 | vals := &Queue[T]{values: []T{}} 1004 | return vals 1005 | } 1006 | 1007 | // Push a new value onto the Queue 1008 | func (s *Queue[T]) Push(f T) { 1009 | s.mu.Lock() 1010 | defer s.mu.Unlock() 1011 | s.values = append(s.values, f) // Simply append the new value to the end of the Queue 1012 | } 1013 | 1014 | // Pop and return top element of Queue. Return false if Queue is empty. 1015 | func (s *Queue[T]) Pop() (T, bool) { 1016 | s.mu.Lock() 1017 | defer s.mu.Unlock() 1018 | if len(s.values) == 0 { 1019 | return *new(T), false 1020 | } else { 1021 | index := len(s.values) - 1 1022 | val := s.values[index] 1023 | s.values = s.values[:index] 1024 | return val, true 1025 | } 1026 | } 1027 | 1028 | // Len returns the length of the queue 1029 | func (s *Queue[T]) Len() int { 1030 | s.mu.RLock() 1031 | defer s.mu.RUnlock() 1032 | return len(s.values) 1033 | } 1034 | 1035 | // Peek returns the next item in the queue without removing it 1036 | func (s *Queue[T]) Peek() (T, bool) { 1037 | s.mu.RLock() 1038 | defer s.mu.RUnlock() 1039 | if len(s.values) == 0 { 1040 | return *new(T), false 1041 | } 1042 | return s.values[len(s.values)-1], true 1043 | } 1044 | 1045 | // Range executes a provided function once for each Queue element until it returns false or the Queue is empty. 1046 | func (q *Queue[T]) Range(fn func(element T) bool) { 1047 | for { 1048 | val, ok := q.Pop() 1049 | if !ok { 1050 | return 1051 | } 1052 | if !fn(val) { 1053 | return 1054 | } 1055 | } 1056 | } 1057 | 1058 | // RangeUntil executes a provided function once for each Queue element until it returns false or a value is sent on the done channel. 1059 | // Use this function when you want to continuously process items from the queue until a done signal is received. 1060 | func (q *Queue[T]) RangeUntil(fn func(element T) bool, done chan struct{}) { 1061 | for { 1062 | select { 1063 | case <-done: 1064 | return 1065 | default: 1066 | val, ok := q.Pop() 1067 | if !ok { 1068 | return 1069 | } 1070 | if !fn(val) { 1071 | return 1072 | } 1073 | } 1074 | } 1075 | } 1076 | 1077 | // NewStack returns a new Stack instance 1078 | func NewStack[T any]() *Stack[T] { 1079 | vals := &Stack[T]{values: []T{}} 1080 | return vals 1081 | } 1082 | 1083 | // Stack is a basic LIFO Stack 1084 | type Stack[T any] struct { 1085 | mu sync.RWMutex 1086 | values []T 1087 | } 1088 | 1089 | // RangeUntil executes a provided function once after calling Pop on the stack until the function returns false or a value is sent on the done channel. 1090 | // Use this function when you want to continuously process items from the stack until a done signal is received. 1091 | func (s *Stack[T]) RangeUntil(fn func(element T) bool, done chan struct{}) { 1092 | for { 1093 | select { 1094 | case <-done: 1095 | return 1096 | default: 1097 | val, ok := s.Pop() 1098 | if !ok { 1099 | return 1100 | } 1101 | if !fn(val) { 1102 | return 1103 | } 1104 | } 1105 | } 1106 | } 1107 | 1108 | // Push a new value onto the Stack (LIFO) 1109 | func (s *Stack[T]) Push(f T) { 1110 | s.mu.Lock() 1111 | defer s.mu.Unlock() 1112 | s.values = append(s.values, f) // Simply append the new value to the end of the Stack 1113 | } 1114 | 1115 | // Pop removes and return top element of Stack. Return false if Stack is empty. 1116 | func (s *Stack[T]) Pop() (T, bool) { 1117 | s.mu.Lock() 1118 | defer s.mu.Unlock() 1119 | if len(s.values) == 0 { 1120 | return *new(T), false 1121 | } else { 1122 | index := len(s.values) - 1 // Get the index of the top most element. 1123 | element := s.values[index] // Index into the slice and obtain the element. 1124 | s.values = s.values[:index] // Remove it from the Stack by slicing it off. 1125 | return element, true 1126 | } 1127 | } 1128 | 1129 | // Clear removes all elements from the Stack 1130 | func (s *Stack[T]) Clear() { 1131 | s.mu.Lock() 1132 | defer s.mu.Unlock() 1133 | s.values = []T{} 1134 | } 1135 | 1136 | // Range executes a provided function once for each Stack element until it returns false. 1137 | func (s *Stack[T]) Range(fn func(element T) bool) { 1138 | for { 1139 | r, ok := s.Pop() 1140 | if !ok { 1141 | return 1142 | } 1143 | if !fn(r) { 1144 | return 1145 | } 1146 | } 1147 | } 1148 | 1149 | // Values returns the values of the stack as an array 1150 | func (s *Stack[T]) Values() []T { 1151 | s.mu.RLock() 1152 | defer s.mu.RUnlock() 1153 | return s.values 1154 | } 1155 | 1156 | // Sort returns the values of the stack as an array sorted by the provided less function 1157 | func (s *Stack[T]) Sort(lessFunc func(i T, j T) bool) []T { 1158 | values := s.Values() 1159 | sort.Slice(values, func(i, j int) bool { 1160 | return lessFunc(values[i], values[j]) 1161 | }) 1162 | return values 1163 | } 1164 | 1165 | // Len returns the number of elements in the Stack. 1166 | func (s *Stack[T]) Len() int { 1167 | s.mu.RLock() 1168 | defer s.mu.RUnlock() 1169 | return len(s.values) 1170 | } 1171 | 1172 | // Peek returns the top element of the Stack without removing it. Return false if Stack is empty. 1173 | func (s *Stack[T]) Peek() (T, bool) { 1174 | s.mu.RLock() 1175 | defer s.mu.RUnlock() 1176 | if len(s.values) == 0 { 1177 | return *new(T), false 1178 | } else { 1179 | index := len(s.values) - 1 // Get the index of the top most element. 1180 | element := s.values[index] // Index into the slice and obtain the element. 1181 | return element, true 1182 | } 1183 | } 1184 | 1185 | // Set is a basic thread-safe Set implementation. 1186 | type Set[T comparable] struct { 1187 | mu sync.RWMutex 1188 | values map[T]struct{} 1189 | } 1190 | 1191 | // NewSet returns a new Set with the given initial size. 1192 | func NewSet[T comparable]() *Set[T] { 1193 | vals := &Set[T]{values: map[T]struct{}{}} 1194 | return vals 1195 | } 1196 | 1197 | // Add adds an element to the Set. 1198 | func (s *Set[T]) Add(val T) { 1199 | s.mu.Lock() 1200 | defer s.mu.Unlock() 1201 | s.values[val] = struct{}{} 1202 | } 1203 | 1204 | // Remove removes an element from the Set. 1205 | func (s *Set[T]) Remove(val T) { 1206 | s.mu.Lock() 1207 | defer s.mu.Unlock() 1208 | delete(s.values, val) 1209 | } 1210 | 1211 | // Contains returns true if the Set contains the element. 1212 | func (s *Set[T]) Contains(val T) bool { 1213 | s.mu.RLock() 1214 | defer s.mu.RUnlock() 1215 | _, ok := s.values[val] 1216 | return ok 1217 | } 1218 | 1219 | // Range executes a provided function once for each Set element until it returns false. 1220 | func (s *Set[T]) Range(fn func(element T) bool) { 1221 | s.mu.RLock() 1222 | defer s.mu.RUnlock() 1223 | for k := range s.values { 1224 | if !fn(k) { 1225 | return 1226 | } 1227 | } 1228 | } 1229 | 1230 | // Len returns the number of elements in the Set. 1231 | func (s *Set[T]) Len() int { 1232 | s.mu.RLock() 1233 | defer s.mu.RUnlock() 1234 | return len(s.values) 1235 | } 1236 | 1237 | // Values returns the values of the set as an array 1238 | func (s *Set[T]) Values() []T { 1239 | s.mu.RLock() 1240 | defer s.mu.RUnlock() 1241 | values := make([]T, len(s.values)) 1242 | for k, _ := range s.values { 1243 | values = append(values, k) 1244 | } 1245 | return values 1246 | } 1247 | 1248 | // Sort returns the values of the set as an array sorted by the provided less function 1249 | func (s *Set[T]) Sort(lessFunc func(i T, j T) bool) []T { 1250 | values := s.Values() 1251 | sort.Slice(values, func(i, j int) bool { 1252 | return lessFunc(values[i], values[j]) 1253 | }) 1254 | return values 1255 | } 1256 | -------------------------------------------------------------------------------- /dagger_test.go: -------------------------------------------------------------------------------- 1 | package dagger_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/autom8ter/dagger/v3" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func init() { 17 | os.Setenv("DAGGER_DEBUG", "true") 18 | } 19 | 20 | type User struct { 21 | id string 22 | Name string 23 | metadata map[string]string 24 | } 25 | 26 | func (u *User) ID() string { 27 | return u.id 28 | } 29 | 30 | func (u *User) Metadata() map[string]string { 31 | return u.metadata 32 | } 33 | 34 | func (u *User) SetMetadata(metadata map[string]string) { 35 | if u.metadata == nil { 36 | u.metadata = map[string]string{} 37 | } 38 | for k, v := range metadata { 39 | u.metadata[k] = v 40 | } 41 | } 42 | 43 | var Jane = &User{ 44 | id: "1", 45 | Name: "Jane", 46 | } 47 | 48 | var John = &User{ 49 | id: "2", 50 | Name: "John", 51 | } 52 | 53 | var Jake = &User{ 54 | id: "3", 55 | Name: "Jake", 56 | } 57 | 58 | var users = []*User{ 59 | Jane, 60 | John, 61 | Jake, 62 | } 63 | 64 | func randUser() *User { 65 | return &User{ 66 | id: strconv.Itoa(int(time.Now().UnixNano())), 67 | Name: "Jane - " + strconv.Itoa(int(time.Now().UnixNano())), 68 | } 69 | } 70 | 71 | func TestGraph(t *testing.T) { 72 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 73 | defer cancel() 74 | t.Run("set node", func(t *testing.T) { 75 | graph, err := dagger.NewDAG[*User]() 76 | assert.NoError(t, err) 77 | for _, user := range users { 78 | node := graph.SetNode(user) 79 | assert.NotNil(t, node) 80 | } 81 | }) 82 | t.Run("set edge", func(t *testing.T) { 83 | graph, err := dagger.NewDAG[*User]() 84 | assert.NoError(t, err) 85 | jane := graph.SetNode(Jane) 86 | john := graph.SetNode(John) 87 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 88 | assert.Nil(t, err) 89 | assert.NotNil(t, edge) 90 | }) 91 | t.Run("set edge with node", func(t *testing.T) { 92 | graph, err := dagger.NewDAG[*User]() 93 | assert.NoError(t, err) 94 | jane := graph.SetNode(Jane) 95 | john := graph.SetNode(John) 96 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 97 | assert.Nil(t, err) 98 | assert.NotNil(t, edge) 99 | }) 100 | t.Run("set edge with node then get", func(t *testing.T) { 101 | graph, err := dagger.NewDAG[*User]() 102 | assert.NoError(t, err) 103 | jane := graph.SetNode(Jane) 104 | john := graph.SetNode(John) 105 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 106 | assert.Nil(t, err) 107 | assert.NotNil(t, edge) 108 | edge, ok := graph.GetEdge(edge.ID()) 109 | assert.True(t, ok) 110 | assert.NotNil(t, edge) 111 | }) 112 | t.Run("check edges from/to", func(t *testing.T) { 113 | graph, err := dagger.NewDAG[*User]() 114 | assert.NoError(t, err) 115 | jane := graph.SetNode(Jane) 116 | john := graph.SetNode(John) 117 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 118 | assert.Nil(t, err) 119 | assert.NotNil(t, edge) 120 | jane.EdgesFrom("", func(e *dagger.GraphEdge[*User]) bool { 121 | assert.Equal(t, e.ID(), edge.ID()) 122 | return true 123 | }) 124 | jane.EdgesTo("", func(e *dagger.GraphEdge[*User]) bool { 125 | assert.NotEqual(t, e.ID(), edge.ID()) 126 | return true 127 | }) 128 | john.EdgesTo("", func(e *dagger.GraphEdge[*User]) bool { 129 | assert.Equal(t, e.ID(), edge.ID()) 130 | return true 131 | }) 132 | john.EdgesFrom("", func(e *dagger.GraphEdge[*User]) bool { 133 | assert.NotEqual(t, e.ID(), edge.ID()) 134 | return true 135 | }) 136 | }) 137 | t.Run("remove node", func(t *testing.T) { 138 | graph, err := dagger.NewDAG[*User]() 139 | assert.NoError(t, err) 140 | jane := graph.SetNode(Jane) 141 | john := graph.SetNode(John) 142 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 143 | assert.Nil(t, err) 144 | assert.NotNil(t, edge) 145 | assert.NoError(t, jane.Remove()) 146 | _, ok := graph.GetNode(jane.ID()) 147 | assert.False(t, ok) 148 | _, ok = graph.GetEdge(edge.ID()) 149 | assert.False(t, ok) 150 | }) 151 | t.Run("remove edge", func(t *testing.T) { 152 | graph, err := dagger.NewDAG[*User]() 153 | assert.NoError(t, err) 154 | jane := graph.SetNode(Jane) 155 | john := graph.SetNode(John) 156 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 157 | assert.Nil(t, err) 158 | assert.NotNil(t, edge) 159 | jane.RemoveEdge(edge.ID()) 160 | _, ok := graph.GetEdge(edge.ID()) 161 | assert.False(t, ok) 162 | 163 | }) 164 | t.Run("graphviz", func(t *testing.T) { 165 | graph, err := dagger.NewDAG[*User](dagger.WithVizualization()) 166 | assert.NoError(t, err) 167 | jane := graph.SetNode(Jane) 168 | john := graph.SetNode(John) 169 | edge, err := jane.SetEdge("knows", john, map[string]string{}) 170 | assert.Nil(t, err) 171 | assert.NotNil(t, edge) 172 | img, err := graph.GraphViz() 173 | assert.NoError(t, err) 174 | assert.NotNil(t, img) 175 | }) 176 | t.Run("breadthFirstSearch", func(t *testing.T) { 177 | graph, err := dagger.NewDAG[*User]() 178 | assert.NoError(t, err) 179 | lastNode := graph.SetNode(Jane) 180 | for i := 0; i < 100; i++ { 181 | node := graph.SetNode(randUser()) 182 | edge, err := lastNode.SetEdge("knows", node, map[string]string{ 183 | "weight": fmt.Sprintf("%d", i), 184 | }) 185 | assert.Nil(t, err) 186 | assert.NotNil(t, edge) 187 | } 188 | nc, ec := graph.Size() 189 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID()) 190 | nodes := make([]*dagger.GraphNode[*User], 0) 191 | assert.NoError(t, graph.BFS(ctx, false, lastNode, func(ctx context.Context, relationship string, node *dagger.GraphNode[*User]) bool { 192 | nodes = append(nodes, node) 193 | return true 194 | })) 195 | 196 | assert.Equal(t, 100, len(nodes)) 197 | }) 198 | t.Run("depthFirstSearch", func(t *testing.T) { 199 | graph, err := dagger.NewDAG[*User]() 200 | assert.NoError(t, err) 201 | lastNode := graph.SetNode(Jane) 202 | for i := 0; i < 100; i++ { 203 | node := graph.SetNode(randUser()) 204 | edge, err := lastNode.SetEdge("knows", node, map[string]string{ 205 | "weight": fmt.Sprintf("%d", i), 206 | }) 207 | assert.Nil(t, err) 208 | assert.NotNil(t, edge) 209 | } 210 | nc, ec := graph.Size() 211 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID()) 212 | nodes := make([]*dagger.GraphNode[*User], 0) 213 | 214 | assert.NoError(t, graph.DFS(ctx, false, lastNode, func(ctx context.Context, relationship string, node *dagger.GraphNode[*User]) bool { 215 | nodes = append(nodes, node) 216 | return true 217 | })) 218 | assert.Equal(t, 100, len(nodes)) 219 | }) 220 | t.Run("acyclic", func(t *testing.T) { 221 | graph, err := dagger.NewDAG[*User]() 222 | assert.NoError(t, err) 223 | lastNode := graph.SetNode(Jane) 224 | for i := 0; i < 100; i++ { 225 | node := graph.SetNode(randUser()) 226 | edge, err := lastNode.SetEdge("knows", node, map[string]string{ 227 | "weight": fmt.Sprintf("%d", i), 228 | }) 229 | assert.Nil(t, err) 230 | assert.NotNil(t, edge) 231 | } 232 | nc, ec := graph.Size() 233 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID()) 234 | assert.True(t, graph.Acyclic()) 235 | }) 236 | t.Run("topological reverse sort", func(t *testing.T) { 237 | graph, err := dagger.NewDAG[*User]() 238 | assert.NoError(t, err) 239 | lastNode := graph.SetNode(Jane) 240 | for i := 0; i < 100; i++ { 241 | node := graph.SetNode(randUser()) 242 | edge, err := lastNode.SetEdge("knows", node, map[string]string{ 243 | "weight": fmt.Sprintf("%d", i), 244 | }) 245 | assert.Nil(t, err) 246 | assert.NotNil(t, edge) 247 | lastNode = node 248 | } 249 | nc, ec := graph.Size() 250 | assert.True(t, graph.Acyclic()) 251 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID()) 252 | nodes, err := graph.TopologicalSort(true) 253 | assert.NoError(t, err) 254 | var n *dagger.GraphNode[*User] 255 | for _, node := range nodes { 256 | if n == nil { 257 | n = node 258 | continue 259 | } 260 | split := strings.Split(n.ID(), "-") 261 | lastNodeID, _ := strconv.Atoi(split[len(split)-1]) 262 | split = strings.Split(node.ID(), "-") 263 | nodeID, _ := strconv.Atoi(split[len(split)-1]) 264 | assert.LessOrEqual(t, lastNodeID, nodeID) 265 | n = node 266 | } 267 | assert.Equal(t, 101, len(nodes)) 268 | }) 269 | t.Run("topological sort", func(t *testing.T) { 270 | graph, err := dagger.NewDAG[*User]() 271 | assert.NoError(t, err) 272 | lastNode := graph.SetNode(Jane) 273 | for i := 0; i < 100; i++ { 274 | node := graph.SetNode(randUser()) 275 | edge, err := lastNode.SetEdge("knows", node, map[string]string{ 276 | "weight": fmt.Sprintf("%d", i), 277 | }) 278 | assert.Nil(t, err) 279 | assert.NotNil(t, edge) 280 | lastNode = node 281 | } 282 | nc, ec := graph.Size() 283 | assert.True(t, graph.Acyclic()) 284 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID()) 285 | nodes, err := graph.TopologicalSort(false) 286 | assert.NoError(t, err) 287 | var n *dagger.GraphNode[*User] 288 | for _, node := range nodes { 289 | if n == nil { 290 | n = node 291 | continue 292 | } 293 | split := strings.Split(n.ID(), "-") 294 | lastNodeID, _ := strconv.Atoi(split[len(split)-1]) 295 | split = strings.Split(node.ID(), "-") 296 | nodeID, _ := strconv.Atoi(split[len(split)-1]) 297 | assert.GreaterOrEqual(t, lastNodeID, nodeID) 298 | n = node 299 | } 300 | assert.Equal(t, 101, len(nodes)) 301 | }) 302 | //t.Run("strongly knows", func(t *testing.T) { 303 | // graph, err := dagger.NewDAG[*User]() 304 | // { 305 | // lastNode := graph.SetNode(Jane) 306 | // for i := 0; i < 50; i++ { 307 | // node := graph.SetNode(randUser()) 308 | // edge, err := lastNode.SetEdge("knows", node, map[string]string{ 309 | // "weight": fmt.Sprintf("%d", i), 310 | // }) 311 | // assert.Nil(t, err) 312 | // assert.NotNil(t, edge) 313 | // lastNode = node 314 | // } 315 | // } 316 | // { 317 | // lastNode := graph.SetNode(dagger.UniqueID("jane")) 318 | // for i := 51; i < 100; i++ { 319 | // node := graph.SetNode(randUser()) 320 | // edge, err := lastNode.SetEdge("knows", node, map[string]string{ 321 | // "weight": fmt.Sprintf("%d", i), 322 | // }) 323 | // assert.Nil(t, err) 324 | // assert.NotNil(t, edge) 325 | // lastNode = node 326 | // } 327 | // } 328 | // assert.True(t, graph.Acyclic()) 329 | // components, err := graph.StronglyConnected() 330 | // assert.NoError(t, err) 331 | // assert.Equal(t, 2, len(components)) 332 | // bits, _ := json.MarshalIndent(components, "", " ") 333 | // t.Logf("strongly knows components: %v", string(bits)) 334 | //}) 335 | } 336 | 337 | func TestBoundedQueue(t *testing.T) { 338 | q := dagger.NewBoundedQueue[string](100) 339 | for i := 0; i < 100; i++ { 340 | q.Push(string(fmt.Sprintf("node-%d", i))) 341 | } 342 | assert.Equal(t, q.Len(), 100) 343 | for i := 0; i < 100; i++ { 344 | v, ok := q.Pop() 345 | assert.NotNil(t, v) 346 | assert.True(t, ok) 347 | } 348 | assert.Equal(t, q.Len(), 0) 349 | } 350 | 351 | func TestStack(t *testing.T) { 352 | s := dagger.NewStack[string]() 353 | for i := 0; i < 100; i++ { 354 | s.Push(string(fmt.Sprintf("node-%d", i))) 355 | } 356 | assert.Equal(t, s.Len(), 100) 357 | for i := 0; i < 100; i++ { 358 | v, ok := s.Pop() 359 | assert.NotNil(t, v) 360 | assert.True(t, ok) 361 | } 362 | assert.Equal(t, s.Len(), 0) 363 | } 364 | 365 | func TestSet(t *testing.T) { 366 | s := dagger.NewSet[string]() 367 | for i := 0; i < 100; i++ { 368 | s.Add(string(fmt.Sprintf("node-%d", i))) 369 | } 370 | assert.Equal(t, s.Len(), 100) 371 | for i := 0; i < 100; i++ { 372 | ok := s.Contains(string(fmt.Sprintf("node-%d", i))) 373 | assert.True(t, ok) 374 | } 375 | assert.Equal(t, s.Len(), 100) 376 | for i := 0; i < 100; i++ { 377 | s.Remove(string(fmt.Sprintf("node-%d", i))) 378 | } 379 | assert.Equal(t, s.Len(), 0) 380 | } 381 | 382 | func TestNewPriorityQueue(t *testing.T) { 383 | q := dagger.NewPriorityQueue[string]() 384 | for i := 0; i < 100; i++ { 385 | q.Push(string(fmt.Sprintf("node-%d", i)), float64(i)) 386 | } 387 | assert.Equal(t, q.Len(), 100) 388 | first, _ := q.Peek() 389 | assert.EqualValues(t, first, string("node-0")) 390 | for i := 0; i < 100; i++ { 391 | v, ok := q.Pop() 392 | assert.NotNil(t, v) 393 | assert.True(t, ok) 394 | } 395 | assert.Equal(t, q.Len(), 0) 396 | } 397 | 398 | func TestQueue(t *testing.T) { 399 | q := dagger.NewQueue[string]() 400 | for i := 0; i < 100; i++ { 401 | q.Push(string(fmt.Sprintf("node-%d", i))) 402 | } 403 | assert.Equal(t, q.Len(), 100) 404 | for i := 0; i < 100; i++ { 405 | v, ok := q.Pop() 406 | assert.NotNil(t, v) 407 | assert.True(t, ok) 408 | } 409 | assert.Equal(t, q.Len(), 0) 410 | } 411 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/autom8ter/dagger/v3 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/goccy/go-graphviz v0.1.1 7 | github.com/stretchr/testify v1.8.4 8 | golang.org/x/sync v0.3.0 9 | ) 10 | 11 | require ( 12 | github.com/autom8ter/async v1.0.1 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/fogleman/gg v1.3.0 // indirect 15 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 16 | github.com/kr/text v0.2.0 // indirect 17 | github.com/pkg/errors v0.9.1 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | golang.org/x/image v0.6.0 // indirect 20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/autom8ter/async v1.0.1 h1:Wr/hEgyq0u1KxaQgnMxZykHDhLy/ot+/1T1U6uI9ir8= 2 | github.com/autom8ter/async v1.0.1/go.mod h1:b0XDjGNFJp+Af0/En4A8CTZxKHe4m/YS43eSd/p1kh0= 3 | github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA= 4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 8 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 9 | github.com/goccy/go-graphviz v0.1.1 h1:MGrsnzBxTyt7KG8FhHsFPDTGvF7UaQMmSa6A610DqPg= 10 | github.com/goccy/go-graphviz v0.1.1/go.mod h1:lpnwvVDjskayq84ZxG8tGCPeZX/WxP88W+OJajh+gFk= 11 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 12 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 13 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 14 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 16 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 17 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 18 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 19 | github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= 20 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 21 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 25 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 26 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 27 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 28 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 29 | golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= 30 | golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= 31 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 32 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 33 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 34 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 35 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 36 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 38 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 41 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 42 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 43 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 49 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 52 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 53 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 54 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 55 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 56 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 57 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 58 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 59 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 60 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 61 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 62 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 63 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 64 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 65 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 66 | --------------------------------------------------------------------------------